This is page 17 of 182. Use http://codebase.md/xmlui-org/xmlui/%7B$item.flickr_images[0]%7D?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .changeset │ ├── config.json │ ├── petite-dogs-live.md │ ├── social-socks-drop.md │ └── warm-lights-lay.md ├── .eslintrc.cjs ├── .github │ ├── build-checklist.png │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows │ ├── deploy-blog.yml │ ├── deploy-docs-optimized.yml │ ├── deploy-docs.yml │ ├── prepare-versions.yml │ ├── release-packages.yml │ ├── run-all-tests.yml │ └── run-smoke-tests.yml ├── .gitignore ├── .prettierrc.js ├── .vscode │ ├── launch.json │ └── settings.json ├── blog │ ├── .gitignore │ ├── .gitkeep │ ├── CHANGELOG.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── layout-changes.md │ ├── package.json │ ├── public │ │ ├── blog │ │ │ ├── images │ │ │ │ ├── blog-page-component.png │ │ │ │ ├── blog-scrabble.png │ │ │ │ ├── integrated-blog-search.png │ │ │ │ └── lorem-ipsum.png │ │ │ ├── lorem-ipsum.md │ │ │ ├── newest-post.md │ │ │ ├── older-post.md │ │ │ └── welcome-to-the-xmlui-blog.md │ │ ├── mockServiceWorker.js │ │ ├── resources │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ └── for-download │ │ │ │ └── xmlui │ │ │ │ └── xmlui-standalone.umd.js │ │ │ ├── github.svg │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ ├── rss.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ └── PageNotFound.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ └── blog-theme.ts │ └── tsconfig.json ├── CONTRIBUTING.md ├── docs │ ├── .gitignore │ ├── CHANGELOG.md │ ├── ComponentRefLinks.txt │ ├── content │ │ ├── _meta.json │ │ ├── components │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── APICall.md │ │ │ ├── App.md │ │ │ ├── AppHeader.md │ │ │ ├── AppState.md │ │ │ ├── AutoComplete.md │ │ │ ├── Avatar.md │ │ │ ├── Backdrop.md │ │ │ ├── Badge.md │ │ │ ├── BarChart.md │ │ │ ├── Bookmark.md │ │ │ ├── Breakout.md │ │ │ ├── Button.md │ │ │ ├── Card.md │ │ │ ├── Carousel.md │ │ │ ├── ChangeListener.md │ │ │ ├── Checkbox.md │ │ │ ├── CHStack.md │ │ │ ├── ColorPicker.md │ │ │ ├── Column.md │ │ │ ├── ContentSeparator.md │ │ │ ├── CVStack.md │ │ │ ├── DataSource.md │ │ │ ├── DateInput.md │ │ │ ├── DatePicker.md │ │ │ ├── DonutChart.md │ │ │ ├── DropdownMenu.md │ │ │ ├── EmojiSelector.md │ │ │ ├── ExpandableItem.md │ │ │ ├── FileInput.md │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FlowLayout.md │ │ │ ├── Footer.md │ │ │ ├── Form.md │ │ │ ├── FormItem.md │ │ │ ├── FormSection.md │ │ │ ├── Fragment.md │ │ │ ├── H1.md │ │ │ ├── H2.md │ │ │ ├── H3.md │ │ │ ├── H4.md │ │ │ ├── H5.md │ │ │ ├── H6.md │ │ │ ├── Heading.md │ │ │ ├── HSplitter.md │ │ │ ├── HStack.md │ │ │ ├── Icon.md │ │ │ ├── IFrame.md │ │ │ ├── Image.md │ │ │ ├── Items.md │ │ │ ├── LabelList.md │ │ │ ├── Legend.md │ │ │ ├── LineChart.md │ │ │ ├── Link.md │ │ │ ├── List.md │ │ │ ├── Logo.md │ │ │ ├── Markdown.md │ │ │ ├── MenuItem.md │ │ │ ├── MenuSeparator.md │ │ │ ├── ModalDialog.md │ │ │ ├── NavGroup.md │ │ │ ├── NavLink.md │ │ │ ├── NavPanel.md │ │ │ ├── NoResult.md │ │ │ ├── NumberBox.md │ │ │ ├── Option.md │ │ │ ├── Page.md │ │ │ ├── PageMetaTitle.md │ │ │ ├── Pages.md │ │ │ ├── Pagination.md │ │ │ ├── PasswordInput.md │ │ │ ├── PieChart.md │ │ │ ├── ProgressBar.md │ │ │ ├── Queue.md │ │ │ ├── RadioGroup.md │ │ │ ├── RealTimeAdapter.md │ │ │ ├── Redirect.md │ │ │ ├── Select.md │ │ │ ├── Slider.md │ │ │ ├── Slot.md │ │ │ ├── SpaceFiller.md │ │ │ ├── Spinner.md │ │ │ ├── Splitter.md │ │ │ ├── Stack.md │ │ │ ├── StickyBox.md │ │ │ ├── SubMenuItem.md │ │ │ ├── Switch.md │ │ │ ├── TabItem.md │ │ │ ├── Table.md │ │ │ ├── TableOfContents.md │ │ │ ├── Tabs.md │ │ │ ├── Text.md │ │ │ ├── TextArea.md │ │ │ ├── TextBox.md │ │ │ ├── Theme.md │ │ │ ├── TimeInput.md │ │ │ ├── Timer.md │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneSwitch.md │ │ │ ├── Tooltip.md │ │ │ ├── Tree.md │ │ │ ├── VSplitter.md │ │ │ ├── VStack.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ ├── xmlui-spreadsheet │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Spreadsheet.md │ │ │ └── xmlui-website-blocks │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Carousel.md │ │ │ ├── HelloMd.md │ │ │ ├── HeroSection.md │ │ │ └── ScrollToTop.md │ │ └── extensions │ │ ├── _meta.json │ │ ├── xmlui-animations │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ ├── Animation.md │ │ │ ├── FadeAnimation.md │ │ │ ├── FadeInAnimation.md │ │ │ ├── FadeOutAnimation.md │ │ │ ├── ScaleAnimation.md │ │ │ └── SlideInAnimation.md │ │ └── xmlui-website-blocks │ │ ├── _meta.json │ │ ├── _overview.md │ │ ├── Carousel.md │ │ ├── HelloMd.md │ │ ├── HeroSection.md │ │ └── ScrollToTop.md │ ├── extensions.ts │ ├── index.html │ ├── index.ts │ ├── package.json │ ├── public │ │ ├── feed.rss │ │ ├── mockServiceWorker.js │ │ ├── pages │ │ │ ├── _meta.json │ │ │ ├── app-structure.md │ │ │ ├── build-editor-component.md │ │ │ ├── build-hello-world-component.md │ │ │ ├── components-intro.md │ │ │ ├── context-variables.md │ │ │ ├── forms.md │ │ │ ├── globals.md │ │ │ ├── glossary.md │ │ │ ├── helper-tags.md │ │ │ ├── hosted-deployment.md │ │ │ ├── howto │ │ │ │ ├── assign-a-complex-json-literal-to-a-component-variable.md │ │ │ │ ├── chain-a-refetch.md │ │ │ │ ├── debug-a-component.md │ │ │ │ ├── delay-a-datasource-until-another-datasource-is-ready.md │ │ │ │ ├── delegate-a-method.md │ │ │ │ ├── do-custom-form-validation.md │ │ │ │ ├── expose-a-method-from-a-component.md │ │ │ │ ├── filter-and-transform-data-from-an-api.md │ │ │ │ ├── group-items-in-list-by-a-property.md │ │ │ │ ├── handle-background-operations.md │ │ │ │ ├── hide-an-element-until-its-datasource-is-ready.md │ │ │ │ ├── make-a-set-of-equal-width-cards.md │ │ │ │ ├── make-a-table-responsive.md │ │ │ │ ├── make-navpanel-width-responsive.md │ │ │ │ ├── modify-a-value-reported-in-a-column.md │ │ │ │ ├── paginate-a-list.md │ │ │ │ ├── pass-data-to-a-modal-dialog.md │ │ │ │ ├── react-to-button-click-not-keystrokes.md │ │ │ │ ├── set-the-initial-value-of-a-select-from-fetched-data.md │ │ │ │ ├── share-a-modaldialog-across-components.md │ │ │ │ ├── sync-selections-between-table-and-list-views.md │ │ │ │ ├── update-ui-optimistically.md │ │ │ │ ├── use-built-in-form-validation.md │ │ │ │ └── use-the-same-modaldialog-to-add-or-edit.md │ │ │ ├── howto.md │ │ │ ├── intro.md │ │ │ ├── layout.md │ │ │ ├── markup.md │ │ │ ├── mcp.md │ │ │ ├── modal-dialogs.md │ │ │ ├── news-and-reviews.md │ │ │ ├── reactive-intro.md │ │ │ ├── refactoring.md │ │ │ ├── routing-and-links.md │ │ │ ├── samples │ │ │ │ ├── color-palette.xmlui │ │ │ │ ├── color-values.xmlui │ │ │ │ ├── shadow-sizes.xmlui │ │ │ │ ├── spacing-sizes.xmlui │ │ │ │ ├── swatch.xmlui │ │ │ │ ├── theme-gallery-brief.xmlui │ │ │ │ └── theme-gallery.xmlui │ │ │ ├── scoping.md │ │ │ ├── scripting.md │ │ │ ├── styles-and-themes │ │ │ │ ├── common-units.md │ │ │ │ ├── layout-props.md │ │ │ │ ├── theme-variable-defaults.md │ │ │ │ ├── theme-variables.md │ │ │ │ └── themes.md │ │ │ ├── template-properties.md │ │ │ ├── test.md │ │ │ ├── tutorial-01.md │ │ │ ├── tutorial-02.md │ │ │ ├── tutorial-03.md │ │ │ ├── tutorial-04.md │ │ │ ├── tutorial-05.md │ │ │ ├── tutorial-06.md │ │ │ ├── tutorial-07.md │ │ │ ├── tutorial-08.md │ │ │ ├── tutorial-09.md │ │ │ ├── tutorial-10.md │ │ │ ├── tutorial-11.md │ │ │ ├── tutorial-12.md │ │ │ ├── universal-properties.md │ │ │ ├── user-defined-components.md │ │ │ ├── vscode.md │ │ │ ├── working-with-markdown.md │ │ │ ├── working-with-text.md │ │ │ ├── xmlui-animations │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── Animation.md │ │ │ │ ├── FadeAnimation.md │ │ │ │ ├── FadeInAnimation.md │ │ │ │ ├── FadeOutAnimation.md │ │ │ │ ├── ScaleAnimation.md │ │ │ │ └── SlideInAnimation.md │ │ │ ├── xmlui-charts │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ ├── BarChart.md │ │ │ │ ├── DonutChart.md │ │ │ │ ├── LabelList.md │ │ │ │ ├── Legend.md │ │ │ │ ├── LineChart.md │ │ │ │ └── PieChart.md │ │ │ ├── xmlui-pdf │ │ │ │ ├── _meta.json │ │ │ │ ├── _overview.md │ │ │ │ └── Pdf.md │ │ │ └── xmlui-spreadsheet │ │ │ ├── _meta.json │ │ │ ├── _overview.md │ │ │ └── Spreadsheet.md │ │ ├── resources │ │ │ ├── devdocs │ │ │ │ ├── debug-proxy-object-2.png │ │ │ │ ├── debug-proxy-object.png │ │ │ │ ├── table_editor_01.png │ │ │ │ ├── table_editor_02.png │ │ │ │ ├── table_editor_03.png │ │ │ │ ├── table_editor_04.png │ │ │ │ ├── table_editor_05.png │ │ │ │ ├── table_editor_06.png │ │ │ │ ├── table_editor_07.png │ │ │ │ ├── table_editor_08.png │ │ │ │ ├── table_editor_09.png │ │ │ │ ├── table_editor_10.png │ │ │ │ ├── table_editor_11.png │ │ │ │ ├── table-editor-01.png │ │ │ │ ├── table-editor-02.png │ │ │ │ ├── table-editor-03.png │ │ │ │ ├── table-editor-04.png │ │ │ │ ├── table-editor-06.png │ │ │ │ ├── table-editor-07.png │ │ │ │ ├── table-editor-08.png │ │ │ │ ├── table-editor-09.png │ │ │ │ └── xmlui-rendering-of-tiptap-markdown.png │ │ │ ├── favicon.ico │ │ │ ├── files │ │ │ │ ├── clients.json │ │ │ │ ├── daily-revenue.json │ │ │ │ ├── dashboard-stats.json │ │ │ │ ├── demo.xmlui │ │ │ │ ├── demo.xmlui.xs │ │ │ │ ├── downloads │ │ │ │ │ └── downloads.json │ │ │ │ ├── for-download │ │ │ │ │ ├── index-with-api.html │ │ │ │ │ ├── index.html │ │ │ │ │ ├── mockApi.js │ │ │ │ │ ├── start-darwin.sh │ │ │ │ │ ├── start-linux.sh │ │ │ │ │ ├── start.bat │ │ │ │ │ └── xmlui │ │ │ │ │ └── xmlui-standalone.umd.js │ │ │ │ ├── getting-started │ │ │ │ │ ├── cl-tutorial-final.zip │ │ │ │ │ ├── cl-tutorial.zip │ │ │ │ │ ├── cl-tutorial2.zip │ │ │ │ │ ├── cl-tutorial3.zip │ │ │ │ │ ├── cl-tutorial4.zip │ │ │ │ │ ├── cl-tutorial5.zip │ │ │ │ │ ├── cl-tutorial6.zip │ │ │ │ │ ├── getting-started.zip │ │ │ │ │ ├── hello-xmlui.zip │ │ │ │ │ ├── xmlui-empty.zip │ │ │ │ │ └── xmlui-starter.zip │ │ │ │ ├── howto │ │ │ │ │ └── component-icons │ │ │ │ │ └── up-arrow.svg │ │ │ │ ├── invoices.json │ │ │ │ ├── monthly-status.json │ │ │ │ ├── news-and-reviews.json │ │ │ │ ├── products.json │ │ │ │ ├── releases.json │ │ │ │ ├── tutorials │ │ │ │ │ ├── datasource │ │ │ │ │ │ └── api.ts │ │ │ │ │ └── p2do │ │ │ │ │ ├── api.ts │ │ │ │ │ └── todo-logo.svg │ │ │ │ └── xmlui.json │ │ │ ├── github.svg │ │ │ ├── images │ │ │ │ ├── apiaction-tutorial │ │ │ │ │ ├── add-success.png │ │ │ │ │ ├── apiaction-param.png │ │ │ │ │ ├── change-completed.png │ │ │ │ │ ├── change-in-progress.png │ │ │ │ │ ├── confirm-delete.png │ │ │ │ │ ├── data-error.png │ │ │ │ │ ├── data-progress.png │ │ │ │ │ ├── data-success.png │ │ │ │ │ ├── display-1.png │ │ │ │ │ ├── item-deleted.png │ │ │ │ │ ├── item-updated.png │ │ │ │ │ ├── missing-api-key.png │ │ │ │ │ ├── new-item-added.png │ │ │ │ │ └── test-message.png │ │ │ │ ├── chat-api │ │ │ │ │ └── domain-model.svg │ │ │ │ ├── components │ │ │ │ │ ├── image │ │ │ │ │ │ └── breakfast.jpg │ │ │ │ │ ├── markdown │ │ │ │ │ │ └── colors.png │ │ │ │ │ └── modal │ │ │ │ │ ├── deep_link_dialog_1.jpg │ │ │ │ │ └── deep_link_dialog_2.jpg │ │ │ │ ├── create-apps │ │ │ │ │ ├── collapsed-vertical.png │ │ │ │ │ ├── using-forms-warning-dialog.png │ │ │ │ │ └── using-forms.png │ │ │ │ ├── datasource-tutorial │ │ │ │ │ ├── data-with-header.png │ │ │ │ │ ├── filtered-data.png │ │ │ │ │ ├── filtered-items.png │ │ │ │ │ ├── initial-page-items.png │ │ │ │ │ ├── list-items.png │ │ │ │ │ ├── next-page-items.png │ │ │ │ │ ├── no-data.png │ │ │ │ │ ├── pagination-1.jpg │ │ │ │ │ ├── pagination-1.png │ │ │ │ │ ├── polling-1.png │ │ │ │ │ ├── refetch-data.png │ │ │ │ │ ├── slow-loading.png │ │ │ │ │ ├── test-message.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── unconventional-data.png │ │ │ │ │ └── unfiltered-items.png │ │ │ │ ├── flower.jpg │ │ │ │ ├── get-started │ │ │ │ │ ├── add-new-contact.png │ │ │ │ │ ├── app-modified.png │ │ │ │ │ ├── app-start.png │ │ │ │ │ ├── app-with-boxes.png │ │ │ │ │ ├── app-with-toast.png │ │ │ │ │ ├── boilerplate-structure.png │ │ │ │ │ ├── cl-initial.png │ │ │ │ │ ├── cl-start.png │ │ │ │ │ ├── contact-counts.png │ │ │ │ │ ├── contact-dialog-title.png │ │ │ │ │ ├── contact-dialog.png │ │ │ │ │ ├── contact-menus.png │ │ │ │ │ ├── contact-predicates.png │ │ │ │ │ ├── context-menu.png │ │ │ │ │ ├── dashboard-numbers.png │ │ │ │ │ ├── default-contact-list.png │ │ │ │ │ ├── delete-contact.png │ │ │ │ │ ├── delete-task.png │ │ │ │ │ ├── detailed-template.png │ │ │ │ │ ├── edit-contact-details.png │ │ │ │ │ ├── edited-contact-saved.png │ │ │ │ │ ├── empty-sections.png │ │ │ │ │ ├── filter-completed.png │ │ │ │ │ ├── fullwidth-desktop.png │ │ │ │ │ ├── fullwidth-mobile.png │ │ │ │ │ ├── initial-table.png │ │ │ │ │ ├── items-and-badges.png │ │ │ │ │ ├── loading-message.png │ │ │ │ │ ├── new-contact-button.png │ │ │ │ │ ├── new-contact-saved.png │ │ │ │ │ ├── no-empty-sections.png │ │ │ │ │ ├── personal-todo-initial.png │ │ │ │ │ ├── piechart.png │ │ │ │ │ ├── review-today.png │ │ │ │ │ ├── rudimentary-dashboard.png │ │ │ │ │ ├── section-collapsed.png │ │ │ │ │ ├── sectioned-items.png │ │ │ │ │ ├── sections-ordered.png │ │ │ │ │ ├── spacex-list-with-links.png │ │ │ │ │ ├── spacex-list.png │ │ │ │ │ ├── start-personal-todo-1.png │ │ │ │ │ ├── submit-new-contact.png │ │ │ │ │ ├── submit-new-task.png │ │ │ │ │ ├── syntax-highlighting.png │ │ │ │ │ ├── table-with-badge.png │ │ │ │ │ ├── template-with-card.png │ │ │ │ │ ├── test-emulated-api.png │ │ │ │ │ ├── Thumbs.db │ │ │ │ │ ├── todo-logo.png │ │ │ │ │ └── xmlui-tools.png │ │ │ │ ├── HelloApp.png │ │ │ │ ├── HelloApp2.png │ │ │ │ ├── logos │ │ │ │ │ ├── xmlui1.svg │ │ │ │ │ ├── xmlui2.svg │ │ │ │ │ ├── xmlui3.svg │ │ │ │ │ ├── xmlui4.svg │ │ │ │ │ ├── xmlui5.svg │ │ │ │ │ ├── xmlui6.svg │ │ │ │ │ └── xmlui7.svg │ │ │ │ ├── pdf │ │ │ │ │ └── dummy-pdf.jpg │ │ │ │ ├── rendering-engine │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ ├── Component.svg │ │ │ │ │ ├── CompoundComponent.svg │ │ │ │ │ ├── RootComponent.svg │ │ │ │ │ └── tree-with-containers.svg │ │ │ │ ├── reviewers-guide │ │ │ │ │ ├── AppEngine-flow.svg │ │ │ │ │ └── incbutton-in-action.png │ │ │ │ ├── tools │ │ │ │ │ └── boilerplate-structure.png │ │ │ │ ├── try.svg │ │ │ │ ├── tutorial │ │ │ │ │ ├── app-chat-history.png │ │ │ │ │ ├── app-content-placeholder.png │ │ │ │ │ ├── app-header-and-content.png │ │ │ │ │ ├── app-links-channel-selected.png │ │ │ │ │ ├── app-links-click.png │ │ │ │ │ ├── app-navigation.png │ │ │ │ │ ├── finished-ex01.png │ │ │ │ │ ├── finished-ex02.png │ │ │ │ │ ├── hello.png │ │ │ │ │ ├── splash-screen-advanced.png │ │ │ │ │ ├── splash-screen-after-click.png │ │ │ │ │ ├── splash-screen-centered.png │ │ │ │ │ ├── splash-screen-events.png │ │ │ │ │ ├── splash-screen-expression.png │ │ │ │ │ ├── splash-screen-reuse-after.png │ │ │ │ │ ├── splash-screen-reuse-before.png │ │ │ │ │ └── splash-screen.png │ │ │ │ └── tutorial-01.png │ │ │ ├── llms.txt │ │ │ ├── logo-dark.svg │ │ │ ├── logo.svg │ │ │ ├── pg-popout.svg │ │ │ └── xmlui-logo.svg │ │ ├── serve.json │ │ └── web.config │ ├── scripts │ │ ├── download-latest-xmlui.js │ │ ├── generate-rss.js │ │ ├── get-releases.js │ │ └── utils.js │ ├── src │ │ ├── components │ │ │ ├── BlogOverview.xmlui │ │ │ ├── BlogPage.xmlui │ │ │ ├── Boxes.xmlui │ │ │ ├── Breadcrumb.xmlui │ │ │ ├── ChangeLog.xmlui │ │ │ ├── ColorPalette.xmlui │ │ │ ├── DocumentLinks.xmlui │ │ │ ├── DocumentPage.xmlui │ │ │ ├── DocumentPageNoTOC.xmlui │ │ │ ├── Icons.xmlui │ │ │ ├── IncButton.xmlui │ │ │ ├── IncButton2.xmlui │ │ │ ├── NameValue.xmlui │ │ │ ├── PageNotFound.xmlui │ │ │ ├── PaletteItem.xmlui │ │ │ ├── Palettes.xmlui │ │ │ ├── SectionHeader.xmlui │ │ │ ├── TBD.xmlui │ │ │ ├── Test.xmlui │ │ │ ├── ThemesIntro.xmlui │ │ │ ├── ThousandThemes.xmlui │ │ │ ├── TubeStops.xmlui │ │ │ ├── TubeStops.xmlui.xs │ │ │ └── TwoColumnCode.xmlui │ │ ├── config.ts │ │ ├── Main.xmlui │ │ └── themes │ │ ├── docs-theme.ts │ │ ├── earthtone.ts │ │ ├── xmlui-gray-on-default.ts │ │ ├── xmlui-green-on-default.ts │ │ └── xmlui-orange-on-default.ts │ └── tsconfig.json ├── LICENSE ├── package-lock.json ├── package.json ├── packages │ ├── xmlui-animations │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── Animation.tsx │ │ │ ├── AnimationNative.tsx │ │ │ ├── FadeAnimation.tsx │ │ │ ├── FadeInAnimation.tsx │ │ │ ├── FadeOutAnimation.tsx │ │ │ ├── index.tsx │ │ │ ├── ScaleAnimation.tsx │ │ │ └── SlideInAnimation.tsx │ │ └── tsconfig.json │ ├── xmlui-devtools │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── devtools │ │ │ │ ├── DevTools.tsx │ │ │ │ ├── DevToolsNative.module.scss │ │ │ │ ├── DevToolsNative.tsx │ │ │ │ ├── ModalDialog.module.scss │ │ │ │ ├── ModalDialog.tsx │ │ │ │ ├── ModalVisibilityContext.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── editor │ │ │ │ └── Editor.tsx │ │ │ └── index.tsx │ │ ├── tsconfig.json │ │ └── vite.config-overrides.ts │ ├── xmlui-hello-world │ │ ├── .gitignore │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── HelloWorld.module.scss │ │ │ ├── HelloWorld.tsx │ │ │ ├── HelloWorldNative.tsx │ │ │ └── index.tsx │ │ └── tsconfig.json │ ├── xmlui-os-frames │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── IPhoneFrame.module.scss │ │ │ ├── IPhoneFrame.tsx │ │ │ ├── MacOSAppFrame.module.scss │ │ │ ├── MacOSAppFrame.tsx │ │ │ ├── WindowsAppFrame.module.scss │ │ │ └── WindowsAppFrame.tsx │ │ └── tsconfig.json │ ├── xmlui-pdf │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ ├── components │ │ │ │ └── Pdf.xmlui │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── LazyPdfNative.tsx │ │ │ ├── Pdf.module.scss │ │ │ └── Pdf.tsx │ │ └── tsconfig.json │ ├── xmlui-playground │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── hooks │ │ │ │ ├── usePlayground.ts │ │ │ │ └── useToast.ts │ │ │ ├── index.tsx │ │ │ ├── playground │ │ │ │ ├── Box.module.scss │ │ │ │ ├── Box.tsx │ │ │ │ ├── CodeSelector.tsx │ │ │ │ ├── ConfirmationDialog.module.scss │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ ├── Editor.tsx │ │ │ │ ├── Header.module.scss │ │ │ │ ├── Header.tsx │ │ │ │ ├── Playground.tsx │ │ │ │ ├── PlaygroundContent.module.scss │ │ │ │ ├── PlaygroundContent.tsx │ │ │ │ ├── PlaygroundNative.module.scss │ │ │ │ ├── PlaygroundNative.tsx │ │ │ │ ├── Preview.module.scss │ │ │ │ ├── Preview.tsx │ │ │ │ ├── Select.module.scss │ │ │ │ ├── StandalonePlayground.tsx │ │ │ │ ├── StandalonePlaygroundNative.module.scss │ │ │ │ ├── StandalonePlaygroundNative.tsx │ │ │ │ ├── ThemeSwitcher.module.scss │ │ │ │ ├── ThemeSwitcher.tsx │ │ │ │ ├── ToneSwitcher.tsx │ │ │ │ ├── Tooltip.module.scss │ │ │ │ ├── Tooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── providers │ │ │ │ ├── Toast.module.scss │ │ │ │ └── ToastProvider.tsx │ │ │ ├── state │ │ │ │ └── store.ts │ │ │ ├── themes │ │ │ │ └── theme.ts │ │ │ └── utils │ │ │ └── helpers.ts │ │ └── tsconfig.json │ ├── xmlui-search │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Search.module.scss │ │ │ └── Search.tsx │ │ └── tsconfig.json │ ├── xmlui-spreadsheet │ │ ├── .gitignore │ │ ├── demo │ │ │ └── Main.xmlui │ │ ├── index.html │ │ ├── index.ts │ │ ├── meta │ │ │ └── componentsMetadata.ts │ │ ├── package.json │ │ ├── src │ │ │ ├── index.tsx │ │ │ ├── Spreadsheet.tsx │ │ │ └── SpreadsheetNative.tsx │ │ └── tsconfig.json │ └── xmlui-website-blocks │ ├── .gitignore │ ├── CHANGELOG.md │ ├── demo │ │ ├── components │ │ │ ├── HeroBackgroundBreakoutPage.xmlui │ │ │ ├── HeroBackgroundsPage.xmlui │ │ │ ├── HeroContentsPage.xmlui │ │ │ ├── HeroTextAlignPage.xmlui │ │ │ ├── HeroTextPage.xmlui │ │ │ └── HeroTonesPage.xmlui │ │ ├── Main.xmlui │ │ └── themes │ │ └── default.ts │ ├── index.html │ ├── index.ts │ ├── meta │ │ └── componentsMetadata.ts │ ├── package.json │ ├── public │ │ └── resources │ │ ├── building.jpg │ │ └── xmlui-logo.svg │ ├── src │ │ ├── Carousel │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ └── CarouselNative.tsx │ │ ├── FancyButton │ │ │ ├── FancyButton.module.scss │ │ │ ├── FancyButton.tsx │ │ │ └── FancyButton.xmlui │ │ ├── Hello │ │ │ ├── Hello.tsx │ │ │ ├── Hello.xmlui │ │ │ └── Hello.xmlui.xs │ │ ├── HeroSection │ │ │ ├── HeroSection.module.scss │ │ │ ├── HeroSection.tsx │ │ │ └── HeroSectionNative.tsx │ │ ├── index.tsx │ │ ├── ScrollToTop │ │ │ ├── ScrollToTop.module.scss │ │ │ ├── ScrollToTop.tsx │ │ │ └── ScrollToTopNative.tsx │ │ └── vite-env.d.ts │ └── tsconfig.json ├── README.md ├── tools │ ├── codefence │ │ └── xmlui-code-fence-docs.md │ ├── create-app │ │ ├── .gitignore │ │ ├── CHANGELOG.md │ │ ├── create-app.ts │ │ ├── helpers │ │ │ ├── copy.ts │ │ │ ├── get-pkg-manager.ts │ │ │ ├── git.ts │ │ │ ├── install.ts │ │ │ ├── is-folder-empty.ts │ │ │ ├── is-writeable.ts │ │ │ ├── make-dir.ts │ │ │ └── validate-pkg.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── templates │ │ │ ├── default │ │ │ │ └── ts │ │ │ │ ├── gitignore │ │ │ │ ├── index.html │ │ │ │ ├── index.ts │ │ │ │ ├── public │ │ │ │ │ ├── mockServiceWorker.js │ │ │ │ │ ├── resources │ │ │ │ │ │ ├── favicon.ico │ │ │ │ │ │ └── xmlui-logo.svg │ │ │ │ │ └── serve.json │ │ │ │ └── src │ │ │ │ ├── components │ │ │ │ │ ├── ApiAware.xmlui │ │ │ │ │ ├── Home.xmlui │ │ │ │ │ ├── IncButton.xmlui │ │ │ │ │ └── PagePanel.xmlui │ │ │ │ ├── config.ts │ │ │ │ └── Main.xmlui │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── tsconfig.json │ ├── create-xmlui-hello-world │ │ ├── index.js │ │ └── package.json │ └── vscode │ ├── .gitignore │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── .vscodeignore │ ├── build.sh │ ├── CHANGELOG.md │ ├── esbuild.js │ ├── eslint.config.mjs │ ├── formatter-docs.md │ ├── generate-test-sample.sh │ ├── LICENSE.md │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── resources │ │ ├── xmlui-logo.png │ │ └── xmlui-markup-syntax-highlighting.png │ ├── src │ │ ├── extension.ts │ │ └── server.ts │ ├── syntaxes │ │ └── xmlui.tmLanguage.json │ ├── test-samples │ │ └── sample.xmlui │ ├── tsconfig.json │ └── tsconfig.tsbuildinfo ├── turbo.json └── xmlui ├── .gitignore ├── bin │ ├── bootstrap.js │ ├── build-lib.ts │ ├── build.ts │ ├── index.ts │ ├── preview.ts │ ├── start.ts │ ├── vite-xmlui-plugin.ts │ └── viteConfig.ts ├── CHANGELOG.md ├── conventions │ ├── component-qa-checklist.md │ ├── copilot-conventions.md │ ├── create-xmlui-components.md │ ├── mermaid.md │ ├── testing-conventions.md │ └── xmlui-in-a-nutshell.md ├── dev-docs │ ├── accessibility.md │ ├── build-system.md │ ├── build-xmlui.md │ ├── component-behaviors.md │ ├── containers.md │ ├── glossary.md │ ├── index.md │ ├── next │ │ ├── component-dev-guide.md │ │ ├── configuration-management-enhancement-summary.md │ │ ├── documentation-scripts-refactoring-complete-summary.md │ │ ├── documentation-scripts-refactoring-plan.md │ │ ├── duplicate-pattern-extraction-summary.md │ │ ├── error-handling-standardization-summary.md │ │ ├── generating-component-reference.md │ │ ├── index.md │ │ ├── logging-consistency-implementation-summary.md │ │ ├── project-build.md │ │ ├── project-structure.md │ │ ├── theme-context.md │ │ ├── tiptap-design-considerations.md │ │ ├── working-with-code.md │ │ ├── xmlui-runtime-architecture │ │ └── xmlui-wcag-accessibility-report.md │ ├── react-fundamentals.md │ ├── release-method.md │ ├── standalone-app.md │ ├── state-management.md │ ├── ud-components.md │ └── xmlui-repo.md ├── package.json ├── playwright.config.ts ├── scripts │ ├── coverage-only.js │ ├── e2e-test-summary.js │ ├── generate-docs │ │ ├── build-downloads-map.mjs │ │ ├── build-pages-map.mjs │ │ ├── components-config.json │ │ ├── configuration-management.mjs │ │ ├── constants.mjs │ │ ├── create-theme-files.mjs │ │ ├── DocsGenerator.mjs │ │ ├── error-handling.mjs │ │ ├── extensions-config.json │ │ ├── folders.mjs │ │ ├── generate-summary-files.mjs │ │ ├── get-docs.mjs │ │ ├── input-handler.mjs │ │ ├── logger.mjs │ │ ├── logging-standards.mjs │ │ ├── MetadataProcessor.mjs │ │ ├── pattern-utilities.mjs │ │ └── utils.mjs │ ├── get-langserver-metadata.mjs │ ├── inline-links.mjs │ └── README-e2e-summary.md ├── src │ ├── abstractions │ │ ├── _conventions.md │ │ ├── ActionDefs.ts │ │ ├── AppContextDefs.ts │ │ ├── ComponentDefs.ts │ │ ├── ContainerDefs.ts │ │ ├── ExtensionDefs.ts │ │ ├── FunctionDefs.ts │ │ ├── RendererDefs.ts │ │ ├── scripting │ │ │ ├── BlockScope.ts │ │ │ ├── Compilation.ts │ │ │ ├── LogicalThread.ts │ │ │ ├── LoopScope.ts │ │ │ ├── modules.ts │ │ │ ├── ScriptParserError.ts │ │ │ ├── Token.ts │ │ │ ├── TryScope.ts │ │ │ └── TryScopeExp.ts │ │ └── ThemingDefs.ts │ ├── components │ │ ├── _conventions.md │ │ ├── abstractions.ts │ │ ├── Accordion │ │ │ ├── Accordion.md │ │ │ ├── Accordion.module.scss │ │ │ ├── Accordion.spec.ts │ │ │ ├── Accordion.tsx │ │ │ ├── AccordionContext.tsx │ │ │ ├── AccordionItem.tsx │ │ │ ├── AccordionItemNative.tsx │ │ │ └── AccordionNative.tsx │ │ ├── Animation │ │ │ └── AnimationNative.tsx │ │ ├── APICall │ │ │ ├── APICall.md │ │ │ ├── APICall.spec.ts │ │ │ ├── APICall.tsx │ │ │ └── APICallNative.tsx │ │ ├── App │ │ │ ├── App.md │ │ │ ├── App.module.scss │ │ │ ├── App.spec.ts │ │ │ ├── App.tsx │ │ │ ├── AppLayoutContext.ts │ │ │ ├── AppNative.tsx │ │ │ ├── AppStateContext.ts │ │ │ ├── doc-resources │ │ │ │ ├── condensed-sticky.xmlui │ │ │ │ ├── condensed.xmlui │ │ │ │ ├── horizontal-sticky.xmlui │ │ │ │ ├── horizontal.xmlui │ │ │ │ ├── vertical-full-header.xmlui │ │ │ │ ├── vertical-sticky.xmlui │ │ │ │ └── vertical.xmlui │ │ │ ├── IndexerContext.ts │ │ │ ├── LinkInfoContext.ts │ │ │ ├── SearchContext.tsx │ │ │ ├── Sheet.module.scss │ │ │ └── Sheet.tsx │ │ ├── AppHeader │ │ │ ├── AppHeader.md │ │ │ ├── AppHeader.module.scss │ │ │ ├── AppHeader.spec.ts │ │ │ ├── AppHeader.tsx │ │ │ └── AppHeaderNative.tsx │ │ ├── AppState │ │ │ ├── AppState.md │ │ │ ├── AppState.spec.ts │ │ │ ├── AppState.tsx │ │ │ └── AppStateNative.tsx │ │ ├── AutoComplete │ │ │ ├── AutoComplete.md │ │ │ ├── AutoComplete.module.scss │ │ │ ├── AutoComplete.spec.ts │ │ │ ├── AutoComplete.tsx │ │ │ ├── AutoCompleteContext.tsx │ │ │ └── AutoCompleteNative.tsx │ │ ├── Avatar │ │ │ ├── Avatar.md │ │ │ ├── Avatar.module.scss │ │ │ ├── Avatar.spec.ts │ │ │ ├── Avatar.tsx │ │ │ └── AvatarNative.tsx │ │ ├── Backdrop │ │ │ ├── Backdrop.md │ │ │ ├── Backdrop.module.scss │ │ │ ├── Backdrop.spec.ts │ │ │ ├── Backdrop.tsx │ │ │ └── BackdropNative.tsx │ │ ├── Badge │ │ │ ├── Badge.md │ │ │ ├── Badge.module.scss │ │ │ ├── Badge.spec.ts │ │ │ ├── Badge.tsx │ │ │ └── BadgeNative.tsx │ │ ├── Bookmark │ │ │ ├── Bookmark.md │ │ │ ├── Bookmark.module.scss │ │ │ ├── Bookmark.spec.ts │ │ │ ├── Bookmark.tsx │ │ │ └── BookmarkNative.tsx │ │ ├── Breakout │ │ │ ├── Breakout.module.scss │ │ │ ├── Breakout.spec.ts │ │ │ ├── Breakout.tsx │ │ │ └── BreakoutNative.tsx │ │ ├── Button │ │ │ ├── Button-style.spec.ts │ │ │ ├── Button.md │ │ │ ├── Button.module.scss │ │ │ ├── Button.spec.ts │ │ │ ├── Button.tsx │ │ │ └── ButtonNative.tsx │ │ ├── Card │ │ │ ├── Card.md │ │ │ ├── Card.module.scss │ │ │ ├── Card.spec.ts │ │ │ ├── Card.tsx │ │ │ └── CardNative.tsx │ │ ├── Carousel │ │ │ ├── Carousel.md │ │ │ ├── Carousel.module.scss │ │ │ ├── Carousel.spec.ts │ │ │ ├── Carousel.tsx │ │ │ ├── CarouselContext.tsx │ │ │ ├── CarouselItem.tsx │ │ │ ├── CarouselItemNative.tsx │ │ │ └── CarouselNative.tsx │ │ ├── ChangeListener │ │ │ ├── ChangeListener.md │ │ │ ├── ChangeListener.spec.ts │ │ │ ├── ChangeListener.tsx │ │ │ └── ChangeListenerNative.tsx │ │ ├── chart-color-schemes.ts │ │ ├── Charts │ │ │ ├── AreaChart │ │ │ │ ├── AreaChart.md │ │ │ │ ├── AreaChart.spec.ts │ │ │ │ ├── AreaChart.tsx │ │ │ │ └── AreaChartNative.tsx │ │ │ ├── BarChart │ │ │ │ ├── BarChart.md │ │ │ │ ├── BarChart.module.scss │ │ │ │ ├── BarChart.spec.ts │ │ │ │ ├── BarChart.tsx │ │ │ │ └── BarChartNative.tsx │ │ │ ├── DonutChart │ │ │ │ ├── DonutChart.spec.ts │ │ │ │ └── DonutChart.tsx │ │ │ ├── LabelList │ │ │ │ ├── LabelList.spec.ts │ │ │ │ ├── LabelList.tsx │ │ │ │ ├── LabelListNative.module.scss │ │ │ │ └── LabelListNative.tsx │ │ │ ├── Legend │ │ │ │ ├── Legend.spec.ts │ │ │ │ ├── Legend.tsx │ │ │ │ └── LegendNative.tsx │ │ │ ├── LineChart │ │ │ │ ├── LineChart.md │ │ │ │ ├── LineChart.module.scss │ │ │ │ ├── LineChart.spec.ts │ │ │ │ ├── LineChart.tsx │ │ │ │ └── LineChartNative.tsx │ │ │ ├── PieChart │ │ │ │ ├── PieChart.md │ │ │ │ ├── PieChart.spec.ts │ │ │ │ ├── PieChart.tsx │ │ │ │ ├── PieChartNative.module.scss │ │ │ │ └── PieChartNative.tsx │ │ │ ├── RadarChart │ │ │ │ ├── RadarChart.md │ │ │ │ ├── RadarChart.spec.ts │ │ │ │ ├── RadarChart.tsx │ │ │ │ └── RadarChartNative.tsx │ │ │ ├── Tooltip │ │ │ │ ├── TooltipContent.module.scss │ │ │ │ ├── TooltipContent.spec.ts │ │ │ │ └── TooltipContent.tsx │ │ │ └── utils │ │ │ ├── abstractions.ts │ │ │ └── ChartProvider.tsx │ │ ├── Checkbox │ │ │ ├── Checkbox.md │ │ │ ├── Checkbox.spec.ts │ │ │ └── Checkbox.tsx │ │ ├── CodeBlock │ │ │ ├── CodeBlock.module.scss │ │ │ ├── CodeBlock.spec.ts │ │ │ ├── CodeBlock.tsx │ │ │ ├── CodeBlockNative.tsx │ │ │ └── highlight-code.ts │ │ ├── collectedComponentMetadata.ts │ │ ├── ColorPicker │ │ │ ├── ColorPicker.md │ │ │ ├── ColorPicker.module.scss │ │ │ ├── ColorPicker.spec.ts │ │ │ ├── ColorPicker.tsx │ │ │ └── ColorPickerNative.tsx │ │ ├── Column │ │ │ ├── Column.md │ │ │ ├── Column.tsx │ │ │ ├── ColumnNative.tsx │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ └── TableContext.tsx │ │ ├── component-utils.ts │ │ ├── ComponentProvider.tsx │ │ ├── ComponentRegistryContext.tsx │ │ ├── container-helpers.tsx │ │ ├── ContentSeparator │ │ │ ├── ContentSeparator.md │ │ │ ├── ContentSeparator.module.scss │ │ │ ├── ContentSeparator.spec.ts │ │ │ ├── ContentSeparator.tsx │ │ │ └── ContentSeparatorNative.tsx │ │ ├── DataSource │ │ │ ├── DataSource.md │ │ │ └── DataSource.tsx │ │ ├── DateInput │ │ │ ├── DateInput.md │ │ │ ├── DateInput.module.scss │ │ │ ├── DateInput.spec.ts │ │ │ ├── DateInput.tsx │ │ │ └── DateInputNative.tsx │ │ ├── DatePicker │ │ │ ├── DatePicker.md │ │ │ ├── DatePicker.module.scss │ │ │ ├── DatePicker.spec.ts │ │ │ ├── DatePicker.tsx │ │ │ └── DatePickerNative.tsx │ │ ├── DropdownMenu │ │ │ ├── DropdownMenu.md │ │ │ ├── DropdownMenu.module.scss │ │ │ ├── DropdownMenu.spec.ts │ │ │ ├── DropdownMenu.tsx │ │ │ ├── DropdownMenuNative.tsx │ │ │ ├── MenuItem.md │ │ │ └── SubMenuItem.md │ │ ├── EmojiSelector │ │ │ ├── EmojiSelector.md │ │ │ ├── EmojiSelector.spec.ts │ │ │ ├── EmojiSelector.tsx │ │ │ └── EmojiSelectorNative.tsx │ │ ├── ExpandableItem │ │ │ ├── ExpandableItem.module.scss │ │ │ ├── ExpandableItem.spec.ts │ │ │ ├── ExpandableItem.tsx │ │ │ └── ExpandableItemNative.tsx │ │ ├── FileInput │ │ │ ├── FileInput.md │ │ │ ├── FileInput.module.scss │ │ │ ├── FileInput.spec.ts │ │ │ ├── FileInput.tsx │ │ │ └── FileInputNative.tsx │ │ ├── FileUploadDropZone │ │ │ ├── FileUploadDropZone.md │ │ │ ├── FileUploadDropZone.module.scss │ │ │ ├── FileUploadDropZone.spec.ts │ │ │ ├── FileUploadDropZone.tsx │ │ │ └── FileUploadDropZoneNative.tsx │ │ ├── FlowLayout │ │ │ ├── FlowLayout.md │ │ │ ├── FlowLayout.module.scss │ │ │ ├── FlowLayout.spec.ts │ │ │ ├── FlowLayout.spec.ts-snapshots │ │ │ │ └── Edge-cases-boxShadow-is-not-clipped-1-non-smoke-darwin.png │ │ │ ├── FlowLayout.tsx │ │ │ └── FlowLayoutNative.tsx │ │ ├── Footer │ │ │ ├── Footer.md │ │ │ ├── Footer.module.scss │ │ │ ├── Footer.spec.ts │ │ │ ├── Footer.tsx │ │ │ └── FooterNative.tsx │ │ ├── Form │ │ │ ├── Form.md │ │ │ ├── Form.module.scss │ │ │ ├── Form.spec.ts │ │ │ ├── Form.tsx │ │ │ ├── formActions.ts │ │ │ ├── FormContext.ts │ │ │ └── FormNative.tsx │ │ ├── FormItem │ │ │ ├── FormItem.md │ │ │ ├── FormItem.module.scss │ │ │ ├── FormItem.spec.ts │ │ │ ├── FormItem.tsx │ │ │ ├── FormItemNative.tsx │ │ │ ├── HelperText.module.scss │ │ │ ├── HelperText.tsx │ │ │ ├── ItemWithLabel.tsx │ │ │ └── Validations.ts │ │ ├── FormSection │ │ │ ├── FormSection.md │ │ │ ├── FormSection.ts │ │ │ └── FormSection.xmlui │ │ ├── Fragment │ │ │ ├── Fragment.spec.ts │ │ │ └── Fragment.tsx │ │ ├── Heading │ │ │ ├── abstractions.ts │ │ │ ├── H1.md │ │ │ ├── H1.spec.ts │ │ │ ├── H2.md │ │ │ ├── H2.spec.ts │ │ │ ├── H3.md │ │ │ ├── H3.spec.ts │ │ │ ├── H4.md │ │ │ ├── H4.spec.ts │ │ │ ├── H5.md │ │ │ ├── H5.spec.ts │ │ │ ├── H6.md │ │ │ ├── H6.spec.ts │ │ │ ├── Heading.md │ │ │ ├── Heading.module.scss │ │ │ ├── Heading.spec.ts │ │ │ ├── Heading.tsx │ │ │ └── HeadingNative.tsx │ │ ├── HoverCard │ │ │ ├── HoverCard.tsx │ │ │ └── HovercardNative.tsx │ │ ├── HtmlTags │ │ │ ├── HtmlTags.module.scss │ │ │ ├── HtmlTags.spec.ts │ │ │ └── HtmlTags.tsx │ │ ├── Icon │ │ │ ├── AdmonitionDanger.tsx │ │ │ ├── AdmonitionInfo.tsx │ │ │ ├── AdmonitionNote.tsx │ │ │ ├── AdmonitionTip.tsx │ │ │ ├── AdmonitionWarning.tsx │ │ │ ├── ApiIcon.tsx │ │ │ ├── ArrowDropDown.module.scss │ │ │ ├── ArrowDropDown.tsx │ │ │ ├── ArrowDropUp.module.scss │ │ │ ├── ArrowDropUp.tsx │ │ │ ├── ArrowLeft.module.scss │ │ │ ├── ArrowLeft.tsx │ │ │ ├── ArrowRight.module.scss │ │ │ ├── ArrowRight.tsx │ │ │ ├── Attach.tsx │ │ │ ├── Binding.module.scss │ │ │ ├── Binding.tsx │ │ │ ├── BoardIcon.tsx │ │ │ ├── BoxIcon.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── ChevronDownIcon.tsx │ │ │ ├── ChevronLeft.tsx │ │ │ ├── ChevronRight.tsx │ │ │ ├── ChevronUpIcon.tsx │ │ │ ├── CodeFileIcon.tsx │ │ │ ├── CodeSandbox.tsx │ │ │ ├── CompactListIcon.tsx │ │ │ ├── ContentCopyIcon.tsx │ │ │ ├── DarkToLightIcon.tsx │ │ │ ├── DatabaseIcon.module.scss │ │ │ ├── DatabaseIcon.tsx │ │ │ ├── DocFileIcon.tsx │ │ │ ├── DocIcon.tsx │ │ │ ├── DotMenuHorizontalIcon.tsx │ │ │ ├── DotMenuIcon.tsx │ │ │ ├── EmailIcon.tsx │ │ │ ├── EmptyFolderIcon.tsx │ │ │ ├── ErrorIcon.tsx │ │ │ ├── ExpressionIcon.tsx │ │ │ ├── FillPlusCricleIcon.tsx │ │ │ ├── FilterIcon.tsx │ │ │ ├── FolderIcon.tsx │ │ │ ├── GlobeIcon.tsx │ │ │ ├── HomeIcon.tsx │ │ │ ├── HyperLinkIcon.tsx │ │ │ ├── Icon.md │ │ │ ├── Icon.module.scss │ │ │ ├── Icon.spec.ts │ │ │ ├── Icon.tsx │ │ │ ├── IconNative.tsx │ │ │ ├── ImageFileIcon.tsx │ │ │ ├── Inspect.tsx │ │ │ ├── LightToDark.tsx │ │ │ ├── LinkIcon.tsx │ │ │ ├── ListIcon.tsx │ │ │ ├── LooseListIcon.tsx │ │ │ ├── MoonIcon.tsx │ │ │ ├── MoreOptionsIcon.tsx │ │ │ ├── NoSortIcon.tsx │ │ │ ├── PDFIcon.tsx │ │ │ ├── PenIcon.tsx │ │ │ ├── PhoneIcon.tsx │ │ │ ├── PhotoIcon.tsx │ │ │ ├── PlusIcon.tsx │ │ │ ├── SearchIcon.tsx │ │ │ ├── ShareIcon.tsx │ │ │ ├── SortAscendingIcon.tsx │ │ │ ├── SortDescendingIcon.tsx │ │ │ ├── StarsIcon.tsx │ │ │ ├── SunIcon.tsx │ │ │ ├── svg │ │ │ │ ├── admonition_danger.svg │ │ │ │ ├── admonition_info.svg │ │ │ │ ├── admonition_note.svg │ │ │ │ ├── admonition_tip.svg │ │ │ │ ├── admonition_warning.svg │ │ │ │ ├── api.svg │ │ │ │ ├── arrow-dropdown.svg │ │ │ │ ├── arrow-left.svg │ │ │ │ ├── arrow-right.svg │ │ │ │ ├── arrow-up.svg │ │ │ │ ├── attach.svg │ │ │ │ ├── binding.svg │ │ │ │ ├── box.svg │ │ │ │ ├── bulb.svg │ │ │ │ ├── code-file.svg │ │ │ │ ├── code-sandbox.svg │ │ │ │ ├── dark_to_light.svg │ │ │ │ ├── database.svg │ │ │ │ ├── doc.svg │ │ │ │ ├── empty-folder.svg │ │ │ │ ├── expression.svg │ │ │ │ ├── eye-closed.svg │ │ │ │ ├── eye-dark.svg │ │ │ │ ├── eye.svg │ │ │ │ ├── file-text.svg │ │ │ │ ├── filter.svg │ │ │ │ ├── folder.svg │ │ │ │ ├── img.svg │ │ │ │ ├── inspect.svg │ │ │ │ ├── light_to_dark.svg │ │ │ │ ├── moon.svg │ │ │ │ ├── pdf.svg │ │ │ │ ├── photo.svg │ │ │ │ ├── share.svg │ │ │ │ ├── stars.svg │ │ │ │ ├── sun.svg │ │ │ │ ├── trending-down.svg │ │ │ │ ├── trending-level.svg │ │ │ │ ├── trending-up.svg │ │ │ │ ├── txt.svg │ │ │ │ ├── unknown-file.svg │ │ │ │ ├── unlink.svg │ │ │ │ └── xls.svg │ │ │ ├── TableDeleteColumnIcon.tsx │ │ │ ├── TableDeleteRowIcon.tsx │ │ │ ├── TableInsertColumnIcon.tsx │ │ │ ├── TableInsertRowIcon.tsx │ │ │ ├── TrashIcon.tsx │ │ │ ├── TrendingDownIcon.tsx │ │ │ ├── TrendingLevelIcon.tsx │ │ │ ├── TrendingUpIcon.tsx │ │ │ ├── TxtIcon.tsx │ │ │ ├── UnknownFileIcon.tsx │ │ │ ├── UnlinkIcon.tsx │ │ │ ├── UserIcon.tsx │ │ │ ├── WarningIcon.tsx │ │ │ └── XlsIcon.tsx │ │ ├── IconProvider.tsx │ │ ├── IconRegistryContext.tsx │ │ ├── IFrame │ │ │ ├── IFrame.md │ │ │ ├── IFrame.module.scss │ │ │ ├── IFrame.spec.ts │ │ │ ├── IFrame.tsx │ │ │ └── IFrameNative.tsx │ │ ├── Image │ │ │ ├── Image.md │ │ │ ├── Image.module.scss │ │ │ ├── Image.spec.ts │ │ │ ├── Image.tsx │ │ │ └── ImageNative.tsx │ │ ├── Input │ │ │ ├── index.ts │ │ │ ├── InputAdornment.module.scss │ │ │ ├── InputAdornment.tsx │ │ │ ├── InputDivider.module.scss │ │ │ ├── InputDivider.tsx │ │ │ ├── InputLabel.module.scss │ │ │ ├── InputLabel.tsx │ │ │ ├── PartialInput.module.scss │ │ │ └── PartialInput.tsx │ │ ├── InspectButton │ │ │ ├── InspectButton.module.scss │ │ │ └── InspectButton.tsx │ │ ├── Items │ │ │ ├── Items.md │ │ │ ├── Items.spec.ts │ │ │ ├── Items.tsx │ │ │ └── ItemsNative.tsx │ │ ├── Link │ │ │ ├── Link.md │ │ │ ├── Link.module.scss │ │ │ ├── Link.spec.ts │ │ │ ├── Link.tsx │ │ │ └── LinkNative.tsx │ │ ├── List │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── List.md │ │ │ ├── List.module.scss │ │ │ ├── List.spec.ts │ │ │ ├── List.tsx │ │ │ └── ListNative.tsx │ │ ├── Logo │ │ │ ├── doc-resources │ │ │ │ └── xmlui-logo.svg │ │ │ ├── Logo.md │ │ │ ├── Logo.tsx │ │ │ └── LogoNative.tsx │ │ ├── Markdown │ │ │ ├── CodeText.module.scss │ │ │ ├── CodeText.tsx │ │ │ ├── Markdown.md │ │ │ ├── Markdown.module.scss │ │ │ ├── Markdown.spec.ts │ │ │ ├── Markdown.tsx │ │ │ ├── MarkdownNative.tsx │ │ │ ├── parse-binding-expr.ts │ │ │ └── utils.ts │ │ ├── metadata-helpers.ts │ │ ├── ModalDialog │ │ │ ├── ConfirmationModalContextProvider.tsx │ │ │ ├── Dialog.module.scss │ │ │ ├── Dialog.tsx │ │ │ ├── ModalDialog.md │ │ │ ├── ModalDialog.module.scss │ │ │ ├── ModalDialog.spec.ts │ │ │ ├── ModalDialog.tsx │ │ │ ├── ModalDialogNative.tsx │ │ │ └── ModalVisibilityContext.tsx │ │ ├── NavGroup │ │ │ ├── NavGroup.md │ │ │ ├── NavGroup.module.scss │ │ │ ├── NavGroup.spec.ts │ │ │ ├── NavGroup.tsx │ │ │ ├── NavGroupContext.ts │ │ │ └── NavGroupNative.tsx │ │ ├── NavLink │ │ │ ├── NavLink.md │ │ │ ├── NavLink.module.scss │ │ │ ├── NavLink.spec.ts │ │ │ ├── NavLink.tsx │ │ │ └── NavLinkNative.tsx │ │ ├── NavPanel │ │ │ ├── NavPanel.md │ │ │ ├── NavPanel.module.scss │ │ │ ├── NavPanel.spec.ts │ │ │ ├── NavPanel.tsx │ │ │ └── NavPanelNative.tsx │ │ ├── NestedApp │ │ │ ├── AppWithCodeView.module.scss │ │ │ ├── AppWithCodeView.tsx │ │ │ ├── AppWithCodeViewNative.tsx │ │ │ ├── defaultProps.tsx │ │ │ ├── logo.svg │ │ │ ├── NestedApp.module.scss │ │ │ ├── NestedApp.tsx │ │ │ ├── NestedAppNative.tsx │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.tsx │ │ │ └── utils.ts │ │ ├── NoResult │ │ │ ├── NoResult.md │ │ │ ├── NoResult.module.scss │ │ │ ├── NoResult.spec.ts │ │ │ ├── NoResult.tsx │ │ │ └── NoResultNative.tsx │ │ ├── NumberBox │ │ │ ├── numberbox-abstractions.ts │ │ │ ├── NumberBox.md │ │ │ ├── NumberBox.module.scss │ │ │ ├── NumberBox.spec.ts │ │ │ ├── NumberBox.tsx │ │ │ └── NumberBoxNative.tsx │ │ ├── Option │ │ │ ├── Option.md │ │ │ ├── Option.spec.ts │ │ │ ├── Option.tsx │ │ │ ├── OptionNative.tsx │ │ │ └── OptionTypeProvider.tsx │ │ ├── PageMetaTitle │ │ │ ├── PageMetaTilteNative.tsx │ │ │ ├── PageMetaTitle.md │ │ │ ├── PageMetaTitle.spec.ts │ │ │ └── PageMetaTitle.tsx │ │ ├── Pages │ │ │ ├── Page.md │ │ │ ├── Pages.md │ │ │ ├── Pages.module.scss │ │ │ ├── Pages.tsx │ │ │ └── PagesNative.tsx │ │ ├── Pagination │ │ │ ├── Pagination.md │ │ │ ├── Pagination.module.scss │ │ │ ├── Pagination.spec.ts │ │ │ ├── Pagination.tsx │ │ │ └── PaginationNative.tsx │ │ ├── PositionedContainer │ │ │ ├── PositionedContainer.module.scss │ │ │ ├── PositionedContainer.tsx │ │ │ └── PositionedContainerNative.tsx │ │ ├── ProfileMenu │ │ │ ├── ProfileMenu.module.scss │ │ │ └── ProfileMenu.tsx │ │ ├── ProgressBar │ │ │ ├── ProgressBar.md │ │ │ ├── ProgressBar.module.scss │ │ │ ├── ProgressBar.spec.ts │ │ │ ├── ProgressBar.tsx │ │ │ └── ProgressBarNative.tsx │ │ ├── Queue │ │ │ ├── Queue.md │ │ │ ├── Queue.spec.ts │ │ │ ├── Queue.tsx │ │ │ ├── queueActions.ts │ │ │ └── QueueNative.tsx │ │ ├── RadioGroup │ │ │ ├── RadioGroup.md │ │ │ ├── RadioGroup.module.scss │ │ │ ├── RadioGroup.spec.ts │ │ │ ├── RadioGroup.tsx │ │ │ ├── RadioGroupNative.tsx │ │ │ ├── RadioItem.tsx │ │ │ └── RadioItemNative.tsx │ │ ├── RealTimeAdapter │ │ │ ├── RealTimeAdapter.tsx │ │ │ └── RealTimeAdapterNative.tsx │ │ ├── Redirect │ │ │ ├── Redirect.md │ │ │ ├── Redirect.spec.ts │ │ │ └── Redirect.tsx │ │ ├── ResponsiveBar │ │ │ ├── README.md │ │ │ ├── ResponsiveBar.md │ │ │ ├── ResponsiveBar.module.scss │ │ │ ├── ResponsiveBar.spec.ts │ │ │ ├── ResponsiveBar.tsx │ │ │ └── ResponsiveBarNative.tsx │ │ ├── Select │ │ │ ├── HiddenOption.tsx │ │ │ ├── MultiSelectOption.tsx │ │ │ ├── OptionContext.ts │ │ │ ├── Select.md │ │ │ ├── Select.module.scss │ │ │ ├── Select.spec.ts │ │ │ ├── Select.tsx │ │ │ ├── SelectContext.tsx │ │ │ ├── SelectNative.tsx │ │ │ ├── SelectOption.tsx │ │ │ └── SimpleSelect.tsx │ │ ├── SelectionStore │ │ │ ├── SelectionStore.md │ │ │ ├── SelectionStore.tsx │ │ │ └── SelectionStoreNative.tsx │ │ ├── Slider │ │ │ ├── Slider.md │ │ │ ├── Slider.module.scss │ │ │ ├── Slider.spec.ts │ │ │ ├── Slider.tsx │ │ │ └── SliderNative.tsx │ │ ├── Slot │ │ │ ├── Slot.md │ │ │ ├── Slot.spec.ts │ │ │ └── Slot.ts │ │ ├── SlotItem.tsx │ │ ├── SpaceFiller │ │ │ ├── SpaceFiller.md │ │ │ ├── SpaceFiller.module.scss │ │ │ ├── SpaceFiller.spec.ts │ │ │ ├── SpaceFiller.tsx │ │ │ └── SpaceFillerNative.tsx │ │ ├── Spinner │ │ │ ├── Spinner.md │ │ │ ├── Spinner.module.scss │ │ │ ├── Spinner.spec.ts │ │ │ ├── Spinner.tsx │ │ │ └── SpinnerNative.tsx │ │ ├── Splitter │ │ │ ├── HSplitter.md │ │ │ ├── HSplitter.spec.ts │ │ │ ├── Splitter.md │ │ │ ├── Splitter.module.scss │ │ │ ├── Splitter.spec.ts │ │ │ ├── Splitter.tsx │ │ │ ├── SplitterNative.tsx │ │ │ ├── utils.ts │ │ │ ├── VSplitter.md │ │ │ └── VSplitter.spec.ts │ │ ├── Stack │ │ │ ├── CHStack.md │ │ │ ├── CHStack.spec.ts │ │ │ ├── CVStack.md │ │ │ ├── CVStack.spec.ts │ │ │ ├── HStack.md │ │ │ ├── HStack.spec.ts │ │ │ ├── Stack.md │ │ │ ├── Stack.module.scss │ │ │ ├── Stack.spec.ts │ │ │ ├── Stack.tsx │ │ │ ├── StackNative.tsx │ │ │ ├── VStack.md │ │ │ └── VStack.spec.ts │ │ ├── StickyBox │ │ │ ├── StickyBox.md │ │ │ ├── StickyBox.module.scss │ │ │ ├── StickyBox.tsx │ │ │ └── StickyBoxNative.tsx │ │ ├── Switch │ │ │ ├── Switch.md │ │ │ ├── Switch.spec.ts │ │ │ └── Switch.tsx │ │ ├── Table │ │ │ ├── doc-resources │ │ │ │ └── list-component-data.js │ │ │ ├── react-table-config.d.ts │ │ │ ├── Table.md │ │ │ ├── Table.module.scss │ │ │ ├── Table.spec.ts │ │ │ ├── Table.tsx │ │ │ ├── TableNative.tsx │ │ │ └── useRowSelection.tsx │ │ ├── TableOfContents │ │ │ ├── TableOfContents.module.scss │ │ │ ├── TableOfContents.spec.ts │ │ │ ├── TableOfContents.tsx │ │ │ └── TableOfContentsNative.tsx │ │ ├── Tabs │ │ │ ├── TabContext.tsx │ │ │ ├── TabItem.md │ │ │ ├── TabItem.tsx │ │ │ ├── TabItemNative.tsx │ │ │ ├── Tabs.md │ │ │ ├── Tabs.module.scss │ │ │ ├── Tabs.spec.ts │ │ │ ├── Tabs.tsx │ │ │ └── TabsNative.tsx │ │ ├── Text │ │ │ ├── Text.md │ │ │ ├── Text.module.scss │ │ │ ├── Text.spec.ts │ │ │ ├── Text.tsx │ │ │ └── TextNative.tsx │ │ ├── TextArea │ │ │ ├── TextArea.md │ │ │ ├── TextArea.module.scss │ │ │ ├── TextArea.spec.ts │ │ │ ├── TextArea.tsx │ │ │ ├── TextAreaNative.tsx │ │ │ ├── TextAreaResizable.tsx │ │ │ └── useComposedRef.ts │ │ ├── TextBox │ │ │ ├── TextBox.md │ │ │ ├── TextBox.module.scss │ │ │ ├── TextBox.spec.ts │ │ │ ├── TextBox.tsx │ │ │ └── TextBoxNative.tsx │ │ ├── Theme │ │ │ ├── NotificationToast.tsx │ │ │ ├── Theme.md │ │ │ ├── Theme.module.scss │ │ │ ├── Theme.spec.ts │ │ │ ├── Theme.tsx │ │ │ └── ThemeNative.tsx │ │ ├── TimeInput │ │ │ ├── TimeInput.md │ │ │ ├── TimeInput.module.scss │ │ │ ├── TimeInput.spec.ts │ │ │ ├── TimeInput.tsx │ │ │ ├── TimeInputNative.tsx │ │ │ └── utils.ts │ │ ├── Timer │ │ │ ├── Timer.md │ │ │ ├── Timer.spec.ts │ │ │ ├── Timer.tsx │ │ │ └── TimerNative.tsx │ │ ├── Toggle │ │ │ ├── Toggle.module.scss │ │ │ └── Toggle.tsx │ │ ├── ToneChangerButton │ │ │ ├── ToneChangerButton.md │ │ │ ├── ToneChangerButton.spec.ts │ │ │ └── ToneChangerButton.tsx │ │ ├── ToneSwitch │ │ │ ├── ToneSwitch.md │ │ │ ├── ToneSwitch.module.scss │ │ │ ├── ToneSwitch.spec.ts │ │ │ ├── ToneSwitch.tsx │ │ │ └── ToneSwitchNative.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.md │ │ │ ├── Tooltip.module.scss │ │ │ ├── Tooltip.spec.ts │ │ │ ├── Tooltip.tsx │ │ │ └── TooltipNative.tsx │ │ ├── Tree │ │ │ ├── testData.ts │ │ │ ├── Tree-dynamic.spec.ts │ │ │ ├── Tree-icons.spec.ts │ │ │ ├── Tree.md │ │ │ ├── Tree.spec.ts │ │ │ ├── TreeComponent.module.scss │ │ │ ├── TreeComponent.tsx │ │ │ └── TreeNative.tsx │ │ ├── TreeDisplay │ │ │ ├── TreeDisplay.md │ │ │ ├── TreeDisplay.module.scss │ │ │ ├── TreeDisplay.tsx │ │ │ └── TreeDisplayNative.tsx │ │ ├── ValidationSummary │ │ │ ├── ValidationSummary.module.scss │ │ │ └── ValidationSummary.tsx │ │ └── VisuallyHidden.tsx │ ├── components-core │ │ ├── abstractions │ │ │ ├── ComponentRenderer.ts │ │ │ ├── LoaderRenderer.ts │ │ │ ├── standalone.ts │ │ │ └── treeAbstractions.ts │ │ ├── action │ │ │ ├── actions.ts │ │ │ ├── APICall.tsx │ │ │ ├── FileDownloadAction.tsx │ │ │ ├── FileUploadAction.tsx │ │ │ ├── NavigateAction.tsx │ │ │ └── TimedAction.tsx │ │ ├── ApiBoundComponent.tsx │ │ ├── appContext │ │ │ ├── date-functions.ts │ │ │ ├── math-function.ts │ │ │ └── misc-utils.ts │ │ ├── AppContext.tsx │ │ ├── behaviors │ │ │ ├── Behavior.tsx │ │ │ └── CoreBehaviors.tsx │ │ ├── component-hooks.ts │ │ ├── ComponentDecorator.tsx │ │ ├── ComponentViewer.tsx │ │ ├── CompoundComponent.tsx │ │ ├── constants.ts │ │ ├── DebugViewProvider.tsx │ │ ├── descriptorHelper.ts │ │ ├── devtools │ │ │ ├── InspectorDialog.module.scss │ │ │ ├── InspectorDialog.tsx │ │ │ └── InspectorDialogVisibilityContext.tsx │ │ ├── EngineError.ts │ │ ├── event-handlers.ts │ │ ├── InspectorButton.module.scss │ │ ├── InspectorContext.tsx │ │ ├── interception │ │ │ ├── abstractions.ts │ │ │ ├── ApiInterceptor.ts │ │ │ ├── ApiInterceptorProvider.tsx │ │ │ ├── apiInterceptorWorker.ts │ │ │ ├── Backend.ts │ │ │ ├── Errors.ts │ │ │ ├── IndexedDb.ts │ │ │ ├── initMock.ts │ │ │ ├── InMemoryDb.ts │ │ │ ├── ReadonlyCollection.ts │ │ │ └── useApiInterceptorContext.tsx │ │ ├── loader │ │ │ ├── ApiLoader.tsx │ │ │ ├── DataLoader.tsx │ │ │ ├── ExternalDataLoader.tsx │ │ │ ├── Loader.tsx │ │ │ ├── MockLoaderRenderer.tsx │ │ │ └── PageableLoader.tsx │ │ ├── LoaderComponent.tsx │ │ ├── markup-check.ts │ │ ├── parts.ts │ │ ├── renderers.ts │ │ ├── rendering │ │ │ ├── AppContent.tsx │ │ │ ├── AppRoot.tsx │ │ │ ├── AppWrapper.tsx │ │ │ ├── buildProxy.ts │ │ │ ├── collectFnVarDeps.ts │ │ │ ├── ComponentAdapter.tsx │ │ │ ├── ComponentWrapper.tsx │ │ │ ├── Container.tsx │ │ │ ├── containers.ts │ │ │ ├── ContainerWrapper.tsx │ │ │ ├── ErrorBoundary.module.scss │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── InvalidComponent.module.scss │ │ │ ├── InvalidComponent.tsx │ │ │ ├── nodeUtils.ts │ │ │ ├── reducer.ts │ │ │ ├── renderChild.tsx │ │ │ ├── StandaloneComponent.tsx │ │ │ ├── StateContainer.tsx │ │ │ ├── UnknownComponent.module.scss │ │ │ ├── UnknownComponent.tsx │ │ │ └── valueExtractor.ts │ │ ├── reportEngineError.ts │ │ ├── RestApiProxy.ts │ │ ├── script-runner │ │ │ ├── asyncProxy.ts │ │ │ ├── AttributeValueParser.ts │ │ │ ├── bannedFunctions.ts │ │ │ ├── BindingTreeEvaluationContext.ts │ │ │ ├── eval-tree-async.ts │ │ │ ├── eval-tree-common.ts │ │ │ ├── eval-tree-sync.ts │ │ │ ├── ParameterParser.ts │ │ │ ├── process-statement-async.ts │ │ │ ├── process-statement-common.ts │ │ │ ├── process-statement-sync.ts │ │ │ ├── ScriptingSourceTree.ts │ │ │ ├── simplify-expression.ts │ │ │ ├── statement-queue.ts │ │ │ └── visitors.ts │ │ ├── StandaloneApp.tsx │ │ ├── StandaloneExtensionManager.ts │ │ ├── TableOfContentsContext.tsx │ │ ├── theming │ │ │ ├── _themes.scss │ │ │ ├── component-layout-resolver.ts │ │ │ ├── extendThemeUtils.ts │ │ │ ├── hvar.ts │ │ │ ├── layout-resolver.ts │ │ │ ├── parse-layout-props.ts │ │ │ ├── StyleContext.tsx │ │ │ ├── StyleRegistry.ts │ │ │ ├── ThemeContext.tsx │ │ │ ├── ThemeProvider.tsx │ │ │ ├── themes │ │ │ │ ├── base-utils.ts │ │ │ │ ├── palette.ts │ │ │ │ ├── root.ts │ │ │ │ ├── solid.ts │ │ │ │ ├── theme-colors.ts │ │ │ │ └── xmlui.ts │ │ │ ├── themeVars.module.scss │ │ │ ├── themeVars.ts │ │ │ ├── transformThemeVars.ts │ │ │ └── utils.ts │ │ ├── utils │ │ │ ├── actionUtils.ts │ │ │ ├── audio-utils.ts │ │ │ ├── base64-utils.ts │ │ │ ├── compound-utils.ts │ │ │ ├── css-utils.ts │ │ │ ├── DataLoaderQueryKeyGenerator.ts │ │ │ ├── date-utils.ts │ │ │ ├── extractParam.ts │ │ │ ├── hooks.tsx │ │ │ ├── LruCache.ts │ │ │ ├── mergeProps.ts │ │ │ ├── misc.ts │ │ │ ├── request-params.ts │ │ │ ├── statementUtils.ts │ │ │ └── treeUtils.ts │ │ └── xmlui-parser.ts │ ├── index-standalone.ts │ ├── index.scss │ ├── index.ts │ ├── language-server │ │ ├── server-common.ts │ │ ├── server-web-worker.ts │ │ ├── server.ts │ │ ├── services │ │ │ ├── common │ │ │ │ ├── docs-generation.ts │ │ │ │ ├── lsp-utils.ts │ │ │ │ ├── metadata-utils.ts │ │ │ │ └── syntax-node-utilities.ts │ │ │ ├── completion.ts │ │ │ ├── diagnostic.ts │ │ │ ├── format.ts │ │ │ └── hover.ts │ │ └── xmlui-metadata-generated.mjs │ ├── logging │ │ ├── LoggerContext.tsx │ │ ├── LoggerInitializer.tsx │ │ ├── LoggerService.ts │ │ └── xmlui.ts │ ├── logo.svg │ ├── parsers │ │ ├── common │ │ │ ├── GenericToken.ts │ │ │ ├── InputStream.ts │ │ │ └── utils.ts │ │ ├── scripting │ │ │ ├── code-behind-collect.ts │ │ │ ├── Lexer.ts │ │ │ ├── modules.ts │ │ │ ├── Parser.ts │ │ │ ├── ParserError.ts │ │ │ ├── ScriptingNodeTypes.ts │ │ │ ├── TokenTrait.ts │ │ │ ├── TokenType.ts │ │ │ └── tree-visitor.ts │ │ ├── style-parser │ │ │ ├── errors.ts │ │ │ ├── source-tree.ts │ │ │ ├── StyleInputStream.ts │ │ │ ├── StyleLexer.ts │ │ │ ├── StyleParser.ts │ │ │ └── tokens.ts │ │ └── xmlui-parser │ │ ├── CharacterCodes.ts │ │ ├── diagnostics.ts │ │ ├── fileExtensions.ts │ │ ├── index.ts │ │ ├── lint.ts │ │ ├── parser.ts │ │ ├── ParserError.ts │ │ ├── scanner.ts │ │ ├── syntax-kind.ts │ │ ├── syntax-node.ts │ │ ├── transform.ts │ │ ├── utils.ts │ │ ├── xmlui-serializer.ts │ │ └── xmlui-tree.ts │ ├── react-app-env.d.ts │ ├── syntax │ │ ├── monaco │ │ │ ├── grammar.monacoLanguage.ts │ │ │ ├── index.ts │ │ │ ├── xmlui-dark.ts │ │ │ ├── xmlui-light.ts │ │ │ └── xmluiscript.monacoLanguage.ts │ │ └── textMate │ │ ├── index.ts │ │ ├── xmlui-dark.json │ │ ├── xmlui-light.json │ │ ├── xmlui.json │ │ └── xmlui.tmLanguage.json │ ├── testing │ │ ├── assertions.ts │ │ ├── component-test-helpers.ts │ │ ├── ComponentDrivers.ts │ │ ├── drivers │ │ │ ├── DateInputDriver.ts │ │ │ ├── ModalDialogDriver.ts │ │ │ ├── NumberBoxDriver.ts │ │ │ ├── TextBoxDriver.ts │ │ │ ├── TimeInputDriver.ts │ │ │ ├── TimerDriver.ts │ │ │ └── TreeDriver.ts │ │ ├── fixtures.ts │ │ ├── infrastructure │ │ │ ├── index.html │ │ │ ├── main.tsx │ │ │ ├── public │ │ │ │ ├── mockServiceWorker.js │ │ │ │ ├── resources │ │ │ │ │ ├── bell.svg │ │ │ │ │ ├── box.svg │ │ │ │ │ ├── doc.svg │ │ │ │ │ ├── eye.svg │ │ │ │ │ ├── flower-640x480.jpg │ │ │ │ │ ├── sun.svg │ │ │ │ │ ├── test-image-100x100.jpg │ │ │ │ │ └── txt.svg │ │ │ │ └── serve.json │ │ │ └── TestBed.tsx │ │ └── themed-app-test-helpers.ts │ └── vite-env.d.ts ├── tests │ ├── components │ │ ├── CodeBlock │ │ │ └── hightlight-code.test.ts │ │ ├── playground-pattern.test.ts │ │ └── Tree │ │ └── Tree-states.test.ts │ ├── components-core │ │ ├── abstractions │ │ │ └── treeAbstractions.test.ts │ │ ├── container │ │ │ └── buildProxy.test.ts │ │ ├── interception │ │ │ ├── orderBy.test.ts │ │ │ ├── ReadOnlyCollection.test.ts │ │ │ └── request-param-converter.test.ts │ │ ├── scripts-runner │ │ │ ├── AttributeValueParser.test.ts │ │ │ ├── eval-tree-arrow-async.test.ts │ │ │ ├── eval-tree-arrow.test.ts │ │ │ ├── eval-tree-func-decl-async.test.ts │ │ │ ├── eval-tree-func-decl.test.ts │ │ │ ├── eval-tree-pre-post.test.ts │ │ │ ├── eval-tree-regression.test.ts │ │ │ ├── eval-tree.test.ts │ │ │ ├── function-proxy.test.ts │ │ │ ├── parser-regression.test.ts │ │ │ ├── process-event.test.ts │ │ │ ├── process-function.test.ts │ │ │ ├── process-implicit-context.test.ts │ │ │ ├── process-statement-asgn.test.ts │ │ │ ├── process-statement-destruct.test.ts │ │ │ ├── process-statement-regs.test.ts │ │ │ ├── process-statement-sync.test.ts │ │ │ ├── process-statement.test.ts │ │ │ ├── process-switch-sync.test.ts │ │ │ ├── process-switch.test.ts │ │ │ ├── process-try-sync.test.ts │ │ │ ├── process-try.test.ts │ │ │ └── test-helpers.ts │ │ ├── test-metadata-handler.ts │ │ ├── theming │ │ │ ├── border-segments.test.ts │ │ │ ├── component-layout.resolver.test.ts │ │ │ ├── layout-property-parser.test.ts │ │ │ ├── layout-resolver.test.ts │ │ │ ├── layout-resolver2.test.ts │ │ │ ├── layout-vp-override.test.ts │ │ │ └── padding-segments.test.ts │ │ └── utils │ │ ├── date-utils.test.ts │ │ ├── format-human-elapsed-time.test.ts │ │ └── LruCache.test.ts │ ├── language-server │ │ ├── completion.test.ts │ │ ├── format.test.ts │ │ ├── hover.test.ts │ │ └── mockData.ts │ └── parsers │ ├── common │ │ └── input-stream.test.ts │ ├── markdown │ │ └── parse-binding-expression.test.ts │ ├── parameter-parser.test.ts │ ├── paremeter-parser.test.ts │ ├── scripting │ │ ├── eval-tree-arrow.test.ts │ │ ├── eval-tree-pre-post.test.ts │ │ ├── eval-tree.test.ts │ │ ├── function-proxy.test.ts │ │ ├── lexer-literals.test.ts │ │ ├── lexer-misc.test.ts │ │ ├── module-parse.test.ts │ │ ├── parser-arrow.test.ts │ │ ├── parser-assignments.test.ts │ │ ├── parser-binary.test.ts │ │ ├── parser-destructuring.test.ts │ │ ├── parser-errors.test.ts │ │ ├── parser-expressions.test.ts │ │ ├── parser-function.test.ts │ │ ├── parser-literals.test.ts │ │ ├── parser-primary.test.ts │ │ ├── parser-regex.test.ts │ │ ├── parser-statements.test.ts │ │ ├── parser-unary.test.ts │ │ ├── process-event.test.ts │ │ ├── process-implicit-context.test.ts │ │ ├── process-statement-asgn.test.ts │ │ ├── process-statement-destruct.test.ts │ │ ├── process-statement-regs.test.ts │ │ ├── process-statement-sync.test.ts │ │ ├── process-statement.test.ts │ │ ├── process-switch-sync.test.ts │ │ ├── process-switch.test.ts │ │ ├── process-try-sync.test.ts │ │ ├── process-try.test.ts │ │ ├── simplify-expression.test.ts │ │ ├── statement-hooks.test.ts │ │ └── test-helpers.ts │ ├── style-parser │ │ ├── generateHvarChain.test.ts │ │ ├── parseHVar.test.ts │ │ ├── parser.test.ts │ │ └── tokens.test.ts │ └── xmlui │ ├── lint.test.ts │ ├── parser.test.ts │ ├── scanner.test.ts │ ├── transform.attr.test.ts │ ├── transform.circular.test.ts │ ├── transform.element.test.ts │ ├── transform.errors.test.ts │ ├── transform.escape.test.ts │ ├── transform.regression.test.ts │ ├── transform.script.test.ts │ ├── transform.test.ts │ └── xmlui.ts ├── tests-e2e │ ├── api-bound-component-regression.spec.ts │ ├── api-call-as-extracted-component.spec.ts │ ├── assign-to-object-or-array-regression.spec.ts │ ├── binding-regression.spec.ts │ ├── children-as-template-context-vars.spec.ts │ ├── compound-component.spec.ts │ ├── context-vars-regression.spec.ts │ ├── data-bindings.spec.ts │ ├── datasource-and-api-usage-in-var.spec.ts │ ├── datasource-direct-binding.spec.ts │ ├── datasource-onLoaded-regression.spec.ts │ ├── modify-array-item-regression.spec.ts │ ├── namespaces.spec.ts │ ├── push-to-array-regression.spec.ts │ ├── screen-breakpoints.spec.ts │ ├── scripting.spec.ts │ ├── state-scope-in-pages.spec.ts │ └── state-var-scopes.spec.ts ├── tsconfig.bin.json ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── vitest.config.ts ``` # Files -------------------------------------------------------------------------------- /xmlui/src/components-core/script-runner/asyncProxy.ts: -------------------------------------------------------------------------------- ```typescript 1 | /** 2 | * Gets a proxy function for one that does not support async operations 3 | * @param fn Function to replace with a proxy 4 | * @param origArgs Original function arguments 5 | * @param context Function context ("this" of the function invocation) 6 | * @return The proxy, if found; otherwise the original function 7 | */ 8 | export function getAsyncProxy(fn: Function, origArgs: any[], context: any): Function { 9 | const proxyFn = asyncProxies.get(fn); 10 | if (!proxyFn) return fn; 11 | 12 | origArgs.unshift(context); 13 | return proxyFn; 14 | } 15 | 16 | // Async implementations for JavaScript functions that do not support async arguments 17 | const asyncProxies = new Map<Function, Function>(); 18 | asyncProxies.set(Array.prototype.filter, asyncFilter); 19 | asyncProxies.set(Array.prototype.forEach, asyncForEach); 20 | asyncProxies.set(Array.prototype.map, asyncMap); 21 | asyncProxies.set(Array.prototype.every, asyncEvery); 22 | asyncProxies.set(Array.prototype.findIndex, asyncFindIndex); 23 | asyncProxies.set(Array.prototype.find, asyncFind); 24 | asyncProxies.set(Array.prototype.flatMap, asyncFlatMap); 25 | asyncProxies.set(Array.prototype.some, asyncSome); 26 | 27 | // The async implementation of Array.prototype.some 28 | async function asyncSome(arr: any[], predicate: (...args: any[]) => boolean) { 29 | const results = await Promise.all(arr.map(predicate)); 30 | return arr.some((_v, index) => results[index]); 31 | } 32 | 33 | // The async implementation of Array.prototype.filter 34 | async function asyncFilter(arr: any[], predicate: (...args: any[]) => boolean) { 35 | const results = await Promise.all(arr.map(predicate)); 36 | return arr.filter((_v, index) => results[index]); 37 | } 38 | 39 | // The async implementation of Array.prototype.forEach 40 | async function asyncForEach(arr: any[], predicate: (...args: any[]) => void) { 41 | for (let i = 0; i < arr.length; i++) { 42 | await predicate(arr[i], i, arr); 43 | } 44 | } 45 | 46 | // The async implementation of Array.prototype.map 47 | async function asyncMap(arr: any[], predicate: (...args: any[]) => Promise<any[]>) { 48 | const result = []; 49 | for (let i = 0; i < arr.length; i++) { 50 | result.push(await predicate(arr[i], i, arr)); 51 | } 52 | return result; 53 | } 54 | 55 | // The async implementation of Array.prototype.asyncEvery 56 | async function asyncEvery(arr: any[], callback: (...args: any[]) => any) { 57 | const results = await Promise.all(arr.map(callback)); 58 | return results.every((_v, index) => results[index]); 59 | } 60 | 61 | // The async implementation of Array.prototype.asyncFind 62 | async function asyncFind(arr: any[], predicate: (...args: any[]) => boolean) { 63 | const results = await Promise.all(arr.map(predicate)); 64 | return arr.find((_v, index) => results[index]); 65 | } 66 | 67 | // The async implementation of Array.prototype.asyncFindIndex 68 | async function asyncFindIndex(arr: any[], predicate: (...args: any[]) => boolean) { 69 | const results = await Promise.all(arr.map(predicate)); 70 | return arr.findIndex((_v, index) => results[index]); 71 | } 72 | 73 | // The async implementation of Array.prototype.asyncFlatMap 74 | async function asyncFlatMap(arr: any[], predicate: (...args: any[]) => boolean) { 75 | const results = await Promise.all(arr.map(predicate)); 76 | return arr.flatMap((_v, index) => results[index]); 77 | } 78 | ``` -------------------------------------------------------------------------------- /docs/public/pages/user-defined-components.md: -------------------------------------------------------------------------------- ```markdown 1 | # User-defined components 2 | 3 | You can define your own components, pass properties to them, and use them interchangeably with core components. When you find yourself writing a component with more than a few dozen lines of XMLUI markup, consider refactoring in order to name and package the key blocks of code. This strategy not only enables reuse but, just as importantly, ensures that the refactored component will be easy to read and maintain. 4 | 5 | Such refactoring requires you to create and name new `.xmlui` files, identify inline elements that need to be passed as properties from a refactored component, and use those properties in newly-created subcomponents. Historically that kind of overhead has been a disincentive to refactoring in any programming environment. Now you can often outsource that gruntwork to AI assistants. It works particularly well with XMLUI, we use this strategy extensively, and we highly recommend it. 6 | 7 | Here's a simple component to package a name/value pair. 8 | 9 | ```xmlui-pg display noHeader 10 | ---app display 11 | <App> 12 | <NameValue name="Mary" value="123" /> 13 | </App> 14 | ---comp display 15 | <Component name="NameValue"> 16 | <Card width="20%"> 17 | <Text>Name: { $props.name} </Text> 18 | <Text>Value: { $props.value} </Text> 19 | </Card> 20 | </Component> 21 | ``` 22 | 23 | The component's name must start with an uppercase letter followed by letters, digits, the underscore (`_`), or the dollar sign (`$`) character. Components must be placed into separate files in the `components` folder within the app's root folder. The component's name must match its filename. 24 | 25 | Here's how you can define default values for properties. 26 | 27 | ```xmlui 28 | <Component name="NameValue"> 29 | <Card width="20%"> 30 | <Text>Name: { $props.name ?? '[no name]' } </Text> 31 | <Text>Value: { $props.value ?? '[no value]' } </Text> 32 | </Card> 33 | </Component> 34 | ``` 35 | 36 | ## Events 37 | 38 | The `<IncButton>` component increments its value for every click, and notifies its environment by firing an event. The event's handler receives the current counter as an event parameter. 39 | 40 | ```xmlui-pg noHeader 41 | ---app display 42 | <App> 43 | <Card width="30%"> 44 | <variable name="text" value=""/> 45 | <IncButton onIncremented="(clickCount) => text += ' ' + clickCount" /> 46 | <Text value="{text}" /> 47 | </Card> 48 | </App> 49 | ---comp display 50 | <Component name="IncButton"> 51 | <variable name="count" value="{0}" /> 52 | <Button 53 | label="Click to increment: {count}" 54 | onClick="count++; emitEvent('incremented', count)" 55 | /> 56 | </Component> 57 | ``` 58 | 59 | ## Methods 60 | 61 | The `<UsingInternalModal>` component exports the `open` method of the `ModalDialog` that it defines. 62 | 63 | ```xmlui-pg noHeader 64 | ---app display 65 | <App height="300px" > 66 | <UsingInternalModal id="component"/> 67 | <Button label="Open the internal dialog" onClick="component.openDialog()" /> 68 | </App> 69 | ---comp display 70 | <Component name="UsingInternalModal"> 71 | <ModalDialog id="dialog" title="Example Dialog"> 72 | <Button label="Close Dialog" onClick="dialog.close()" /> 73 | </ModalDialog> 74 | 75 | <H1>Using an Internal Modal Dialog</H1> 76 | 77 | <method name="openDialog"> 78 | console.log('internal method called') 79 | dialog.open(); 80 | </method> 81 | </Component> 82 | ``` 83 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Accordion/Accordion.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | $component: "Accordion"; 11 | $themeVars: t.composeBorderVars($themeVars, $component); 12 | $themeVars: t.composePaddingVars($themeVars, $component); 13 | $borderRadius: createThemeVar("borderRadius-#{$component}"); 14 | 15 | 16 | @layer components { 17 | .root { 18 | overflow: hidden; 19 | width: 100%; 20 | //box-shadow: 0 2px 10px var(--black-a4); 21 | @include t.borderVars($themeVars, $component); 22 | @include t.paddingVars($themeVars, $component); 23 | } 24 | 25 | .item { 26 | overflow: hidden; 27 | margin-top: 1px; 28 | box-sizing: border-box; 29 | 30 | &:first-child { 31 | margin-top: 0; 32 | border-top-left-radius: $borderRadius; 33 | border-top-right-radius: $borderRadius; 34 | } 35 | 36 | &:last-child { 37 | border-bottom-left-radius: $borderRadius; 38 | border-bottom-right-radius: $borderRadius; 39 | } 40 | 41 | &:focus-within { 42 | position: relative; 43 | z-index: 1; 44 | } 45 | } 46 | 47 | .header { 48 | display: flex; 49 | margin: 0; 50 | } 51 | 52 | .trigger { 53 | font-family: inherit; 54 | flex: 1; 55 | display: inline-flex; 56 | align-items: createThemeVar("verticalAlignment-header-#{$component}"); 57 | justify-content: space-between; 58 | line-height: 1; 59 | box-sizing: border-box; 60 | font-size: createThemeVar("fontSize-header-#{$component}"); 61 | font-weight: createThemeVar("fontWeight-header-#{$component}"); 62 | font-style: createThemeVar("fontStyle-header-#{$component}"); 63 | padding: createThemeVar("paddingVertical-header-#{$component}") createThemeVar("paddingHorizontal-header-#{$component}"); 64 | background-color: createThemeVar("backgroundColor-header-#{$component}"); 65 | color: createThemeVar("color-header-#{$component}"); 66 | 67 | &:hover { 68 | background-color: createThemeVar("backgroundColor-header-#{$component}-hover"); 69 | } 70 | 71 | &.triggerStart { 72 | flex-direction: row-reverse; 73 | } 74 | } 75 | 76 | .contentWrapper { 77 | overflow: hidden; 78 | color: createThemeVar("color-content-#{$component}"); 79 | background-color: createThemeVar("backgroundColor-content-#{$component}"); 80 | 81 | &[data-state='open'] { 82 | animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1); 83 | } 84 | 85 | &[data-state='closed'] { 86 | animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1); 87 | } 88 | } 89 | 90 | .content { 91 | padding: t.$space-2; 92 | } 93 | 94 | .chevron { 95 | width: createThemeVar("width-icon-#{$component}"); 96 | height: createThemeVar("height-icon-#{$component}"); 97 | color: createThemeVar("color-icon-#{$component}"); 98 | } 99 | 100 | @keyframes slideDown { 101 | from { 102 | height: 0; 103 | } 104 | to { 105 | height: var(--radix-accordion-content-height); 106 | } 107 | } 108 | 109 | @keyframes slideUp { 110 | from { 111 | height: var(--radix-accordion-content-height); 112 | } 113 | to { 114 | height: 0; 115 | } 116 | } 117 | } 118 | 119 | 120 | 121 | // --- We export the theme variables to add them to the component renderer 122 | :export { 123 | themeVars: t.json-stringify($themeVars); 124 | } 125 | ``` -------------------------------------------------------------------------------- /packages/xmlui-animations/src/FadeAnimation.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { createComponentRenderer, createMetadata } from "xmlui"; 2 | import { Animation, defaultProps } from "./AnimationNative"; 3 | 4 | const COMP = "FadeAnimation"; 5 | 6 | const defaultAnimationValues = { 7 | from: 0, 8 | to: 1, 9 | }; 10 | 11 | export const FadeAnimationMd = createMetadata({ 12 | status: "experimental", 13 | description: `The \`${COMP}\` component represents an animation that fades the content with adjustable opacity values.`, 14 | docFolder: "src", 15 | props: { 16 | from: { 17 | description: 18 | "Sets the initial opacity of the content." + 19 | "If the `to` property is not set, the initial opacity set here will be used as the final opacity.", 20 | valueType: "number", 21 | defaultValue: defaultAnimationValues.from, 22 | }, 23 | to: { 24 | description: 25 | "Sets the final opacity of the content." + 26 | "If the `from` property is not set, the initial opacity set here will be used as the final opacity.", 27 | valueType: "number", 28 | defaultValue: defaultAnimationValues.to, 29 | }, 30 | animateWhenInView: { 31 | description: `Indicates whether the animation should start when the component is in view`, 32 | valueType: "boolean", 33 | }, 34 | duration: { 35 | description: `The duration of the animation in milliseconds`, 36 | valueType: "number", 37 | }, 38 | reverse: { 39 | description: `Indicates whether the animation should run in reverse`, 40 | defaultValue: defaultProps.reverse, 41 | valueType: "boolean", 42 | }, 43 | loop: { 44 | description: `Indicates whether the animation should loop`, 45 | defaultValue: defaultProps.loop, 46 | valueType: "boolean", 47 | }, 48 | delay: { 49 | description: `The delay before the animation starts in milliseconds`, 50 | defaultValue: defaultProps.delay, 51 | valueType: "number", 52 | }, 53 | }, 54 | events: { 55 | started: { description: `Event fired when the animation starts` }, 56 | stopped: { description: `Event fired when the animation stops` }, 57 | }, 58 | apis: { 59 | start: { description: `Starts the animation` }, 60 | stop: { description: `Stops the animation` }, 61 | }, 62 | }); 63 | 64 | export const fadeAnimationRenderer = createComponentRenderer( 65 | "FadeAnimation", 66 | FadeAnimationMd, 67 | ({ node, renderChild, extractValue, registerComponentApi, lookupEventHandler }) => { 68 | return ( 69 | <Animation 70 | registerComponentApi={registerComponentApi} 71 | animation={{ 72 | from: { 73 | opacity: extractValue.asOptionalNumber(node.props.from, defaultAnimationValues.from), 74 | }, 75 | to: { 76 | opacity: extractValue.asOptionalNumber(node.props.to, defaultAnimationValues.to), 77 | }, 78 | }} 79 | duration={extractValue.asOptionalNumber(node.props.duration)} 80 | onStop={lookupEventHandler("stopped")} 81 | onStart={lookupEventHandler("started")} 82 | animateWhenInView={extractValue.asOptionalBoolean(node.props.animateWhenInView)} 83 | reverse={extractValue.asOptionalBoolean(node.props.reverse)} 84 | loop={extractValue.asOptionalBoolean(node.props.loop)} 85 | delay={extractValue.asOptionalNumber(node.props.delay)} 86 | > 87 | {renderChild(node.children)} 88 | </Animation> 89 | ); 90 | }, 91 | ); 92 | ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/xmlui/transform.escape.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | import { ComponentDef } from "../../../src/abstractions/ComponentDefs"; 3 | import { transformSource } from "./xmlui"; 4 | 5 | describe("Xmlui transform - no backslash escape", () => { 6 | it("backslash", () => { 7 | const cd = transformSource("<Stack a='\\\\' />") as ComponentDef; 8 | expect((cd.props as any).a).equal("\\\\"); 9 | }); 10 | 11 | it("newline", () => { 12 | const cd = transformSource("<Stack a='\\n' />") as ComponentDef; 13 | expect((cd.props as any).a).equal("\\n"); 14 | }); 15 | }); 16 | 17 | describe("Xmlui transform - entity escape", () => { 18 | it("&", () => { 19 | const cd = transformSource("<Stack>&</Stack>") as ComponentDef; 20 | expect((cd.children![0].props as any).value).toEqual("&"); 21 | }); 22 | 23 | it(">", () => { 24 | const cd = transformSource("<Stack>></Stack>") as ComponentDef; 25 | expect((cd.children![0].props as any).value).toEqual(">"); 26 | }); 27 | 28 | it("<", () => { 29 | const cd = transformSource("<Stack><</Stack>") as ComponentDef; 30 | expect((cd.children![0].props as any).value).toEqual("<"); 31 | }); 32 | it("'", () => { 33 | const cd = transformSource("<Stack>'</Stack>") as ComponentDef; 34 | expect((cd.children![0].props as any).value).toEqual("'"); 35 | }); 36 | it(""", () => { 37 | const cd = transformSource("<Stack>"</Stack>") as ComponentDef; 38 | expect((cd.children![0].props as any).value).toEqual('"'); 39 | }); 40 | it(" ", () => { 41 | const cd = transformSource("<Stack> </Stack>") as ComponentDef; 42 | expect((cd.children![0].props as any).value).toEqual("\xa0\xa0\xa0"); 43 | }); 44 | 45 | it("combined", () => { 46 | const cd = transformSource("<Stack>"&abc><</Stack>") as ComponentDef; 47 | expect((cd.children![0].props as any).value).toEqual('"&abc><'); 48 | }); 49 | 50 | it("& ignored inside CData", () => { 51 | const cd = transformSource("<Stack><![CDATA[&]]></Stack>") as ComponentDef; 52 | expect((cd.children![0].props as any).value).toEqual("&"); 53 | }); 54 | 55 | it("attr - &", () => { 56 | const cd = transformSource("<H1 value='&' />") as ComponentDef; 57 | expect((cd.props as any).value).toEqual("&"); 58 | }); 59 | 60 | it("attr - >", () => { 61 | const cd = transformSource("<H1 value='>' />") as ComponentDef; 62 | expect((cd.props as any).value).toEqual(">"); 63 | }); 64 | 65 | it("attr - ", () => { 66 | const cd = transformSource("<H1 value=' ' />") as ComponentDef; 67 | expect((cd.props as any).value).toEqual("\xa0\xa0\xa0"); 68 | }); 69 | 70 | it("attr - <", () => { 71 | const cd = transformSource("<H1 value='<' />") as ComponentDef; 72 | expect((cd.props as any).value).toEqual("<"); 73 | }); 74 | it("attr - '", () => { 75 | const cd = transformSource("<H1 value=''' />") as ComponentDef; 76 | expect((cd.props as any).value).toEqual("'"); 77 | }); 78 | it("attr - "", () => { 79 | const cd = transformSource("<H1 value='"' />") as ComponentDef; 80 | expect((cd.props as any).value).toEqual('"'); 81 | }); 82 | 83 | it("attr - combined", () => { 84 | const cd = transformSource("<H1 value='"&abc>' />") as ComponentDef; 85 | expect((cd.props as any).value).toEqual('"&abc>'); 86 | }); 87 | }); 88 | ``` -------------------------------------------------------------------------------- /docs/public/resources/files/products.json: -------------------------------------------------------------------------------- ```json 1 | [{"id":14,"name":"API Integration","description":"Third-party API integration services","price":105,"created_at":"2022-08-10T14:06:49.463Z"}, 2 | {"id":22,"name":"Brand Strategy Consulting","description":"Strategic brand positioning and messaging","price":115,"created_at":"2025-06-06 00:02:08"}, 3 | {"id":8,"name":"Cloud Migration","description":"Migrate systems to cloud infrastructure","price":130,"created_at":"2022-10-20T02:24:30.991Z"}, 4 | {"id":6,"name":"Content Creation","description":"Blog posts and content marketing","price":65,"created_at":"2023-02-27T14:54:28.774Z"}, 5 | {"id":13,"name":"Data Analysis","description":"Data analytics and reporting","price":90,"created_at":"2022-05-10T21:05:04.425Z"}, 6 | {"id":7,"name":"Database Design","description":"Database architecture and optimization","price":110,"created_at":"2022-02-06T12:47:47.397Z"}, 7 | {"id":19,"name":"Domain Registration (Yearly)","description":"Domain name registration and renewal","price":15,"created_at":"2023-03-08T06:10:10.884Z"}, 8 | {"id":21,"name":"E-commerce Development","description":"Online store development and integration","price":135,"created_at":"2025-06-06 00:02:08"}, 9 | {"id":12,"name":"Email Marketing","description":"Email campaign setup and management","price":60,"created_at":"2022-03-07T00:58:53.758Z"}, 10 | {"id":11,"name":"Logo Design","description":"Professional logo design services","price":80,"created_at":"2022-06-29T23:21:01.184Z"}, 11 | {"id":4,"name":"Mobile App Development","description":"Native mobile application development","price":145,"created_at":"2022-04-27T01:31:14.995Z"}, 12 | {"id":15,"name":"Project Management","description":"Professional project management services","price":95,"created_at":"2022-07-29T06:59:59.756Z"}, 13 | {"id":16,"name":"QA Testing","description":"Software quality assurance testing","price":80,"created_at":"2022-05-19T05:35:07.028Z"}, 14 | {"id":5,"name":"SEO Optimization","description":"Search engine optimization services","price":85,"created_at":"2022-09-27T13:12:58.057Z"}, 15 | {"id":9,"name":"Security Audit","description":"Comprehensive security assessment","price":150,"created_at":"2022-02-26T22:17:19.018Z"}, 16 | {"id":3,"name":"Server Maintenance","description":"Monthly server maintenance and updates","price":75,"created_at":"2022-02-27T00:11:24.402Z"}, 17 | {"id":10,"name":"Social Media Management","description":"Monthly social media account management","price":70,"created_at":"2022-03-17T19:11:09.894Z"}, 18 | {"id":17,"name":"Tech Consultation","description":"Technology consultation services (hourly)","price":125,"created_at":"2022-07-09T22:04:19.424Z"}, 19 | {"id":2,"name":"UI/UX Design","description":"User interface and experience design","price":95,"created_at":"2022-02-08T19:22:16.697Z"}, 20 | {"id":20,"name":"Video Production","description":"Professional video production services","price":175,"created_at":"2023-05-07T22:52:06.967Z"}, 21 | {"id":1,"name":"Web Development","description":"Full stack web application development","price":120,"created_at":"2022-12-24T10:09:20.570Z"}, 22 | {"id":18,"name":"Website Hosting (Monthly)","description":"Managed website hosting services","price":45,"created_at":"2022-02-28T15:52:17.105Z"}] 23 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/FlowLayout/FlowLayout.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import styles from "./FlowLayout.module.scss"; 2 | 3 | import { createComponentRenderer } from "../../components-core/renderers"; 4 | import { isComponentDefChildren } from "../../components-core/utils/misc"; 5 | import { NotAComponentDefError } from "../../components-core/EngineError"; 6 | import { parseScssVar } from "../../components-core/theming/themeVars"; 7 | import { FlowItemBreak, FlowItemWrapper, FlowLayout, defaultProps } from "./FlowLayoutNative"; 8 | import { createMetadata } from "../metadata-helpers"; 9 | 10 | const COMP = "FlowLayout"; 11 | 12 | export const FlowLayoutMd = createMetadata({ 13 | status: "stable", 14 | description: 15 | "`FlowLayout` positions content in rows with automatic wrapping. When items " + 16 | "exceed the available horizontal space, they automatically wrap to a new line.", 17 | props: { 18 | gap: { 19 | description: 20 | `This property defines the gap between items in the same row and between rows. The ${COMP} ` + 21 | `component creates a new row when an item is about to overflow the current row.`, 22 | type: "string", 23 | defaultValue: "$gap-normal", 24 | }, 25 | columnGap: { 26 | description: 27 | "The \`columnGap\` property specifies the space between items in a single row; it overrides " + 28 | "the \`gap\` value.", 29 | defaultValue: defaultProps.columnGap, 30 | }, 31 | rowGap: { 32 | description: 33 | `The \`rowGap\` property specifies the space between the ${COMP} rows; it overrides ` + 34 | `the \`gap\` value.`, 35 | defaultValue: defaultProps.rowGap, 36 | }, 37 | }, 38 | themeVars: parseScssVar(styles.themeVars), 39 | }); 40 | 41 | export const flowLayoutComponentRenderer = createComponentRenderer( 42 | COMP, 43 | FlowLayoutMd, 44 | ({ node, renderChild, className, extractValue }) => { 45 | if (!isComponentDefChildren(node.children)) { 46 | throw new NotAComponentDefError(); 47 | } 48 | 49 | const columnGap = 50 | extractValue.asSize(node.props?.columnGap) || 51 | extractValue.asSize(node.props?.gap) || 52 | extractValue.asSize("$space-4"); 53 | const rowGap = 54 | extractValue.asSize(node.props?.rowGap) || extractValue.asSize(node.props?.gap) || extractValue.asSize("$space-4"); 55 | 56 | return ( 57 | <FlowLayout className={className} columnGap={columnGap} rowGap={rowGap}> 58 | {renderChild(node.children, { 59 | wrapChild: ({ node, extractValue }, renderedChild, hints) => { 60 | if (hints?.opaque) { 61 | return renderedChild; 62 | } 63 | // Handle SpaceFiller as flow item break 64 | if (node.type === "SpaceFiller") { 65 | return <FlowItemBreak force={true} />; 66 | } 67 | const width = extractValue((node.props as any)?.width); 68 | const minWidth = extractValue((node.props as any)?.minWidth); 69 | const maxWidth = extractValue((node.props as any)?.maxWidth); 70 | return ( 71 | <FlowItemWrapper 72 | width={width} 73 | minWidth={minWidth} 74 | maxWidth={maxWidth} 75 | forceBreak={node.type === "SpaceFiller"} 76 | > 77 | {renderedChild} 78 | </FlowItemWrapper> 79 | ); 80 | }, 81 | })} 82 | </FlowLayout> 83 | ); 84 | }, 85 | ); 86 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Markdown/CodeText.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | $themeVars: (); 4 | @function createThemeVar($componentVariable) { 5 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 6 | @return t.getThemeVar($themeVars, $componentVariable); 7 | } 8 | 9 | $component: "Text"; 10 | 11 | @mixin textVariant($variant) { 12 | $variantName: if($variant == "", "#{$component}", "#{$component}-#{$variant}"); 13 | $themeVars: t.composePaddingVars($themeVars, $variantName); 14 | $themeVars: t.composeBorderVars($themeVars, $variantName); 15 | $themeVars: t.composeTextVars($themeVars, $variantName, $component); 16 | @include t.paddingVars($themeVars, $variantName); 17 | @include t.borderVars($themeVars, $variantName); 18 | @include t.textVars($themeVars, $variantName); 19 | margin-top: createThemeVar("marginTop-#{$variantName}"); 20 | margin-bottom: createThemeVar("marginBottom-#{$variantName}"); 21 | margin-left: createThemeVar("marginLeft-#{$variantName}"); 22 | margin-right: createThemeVar("marginRight-#{$variantName}"); 23 | vertical-align: createThemeVar("verticalAlignment-#{$variantName}"); 24 | } 25 | 26 | @layer components { 27 | .codeText { 28 | overflow: hidden; 29 | text-overflow: ellipsis; 30 | margin: 0; 31 | padding: 0; 32 | min-height: fit-content; 33 | 34 | // --- Styling <pre><code> elements 35 | 36 | // codefence/<pre> elements are block level by default, even in another text element 37 | // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre 38 | .text.codefence { 39 | display: block; 40 | margin-top: createThemeVar("marginTop-#{$component}-codefence"); 41 | margin-bottom: createThemeVar("marginBottom-#{$component}-codefence"); 42 | height: 100%; 43 | } 44 | 45 | // For inline code 46 | :not(.text.codefence) > &.code, 47 | :not(.text.codefence) > &code { 48 | @include textVariant("code"); 49 | } 50 | 51 | // For code blocks using <![CDATA[ + <code> tags 52 | &.codefence:has(> .text.code), 53 | &.codefence:has(> code) { 54 | @include textVariant("codefence"); 55 | // TEMP: solution for a demo - need to re-evaluate how to truncate/wrap code blocks 56 | overflow-x: auto; 57 | text-overflow: initial; 58 | } 59 | 60 | // TEMP: experimental - may be removed 61 | &.codefence:has(> code > span) { 62 | padding-inline: 0; 63 | } 64 | &.codefence > code > span:not([class*="numbered"]) { 65 | padding-inline: t.$space-3; 66 | } 67 | 68 | &.codefence > code { 69 | font-family: createThemeVar("fontFamily-#{$component}-codefence"); 70 | //white-space: pre-wrap; 71 | font-family: createThemeVar("fontFamily-#{$component}-codefence"); 72 | //display: inline-block; 73 | width: 100%; 74 | } 75 | 76 | &.codefence > code::before { 77 | counter-reset: listing; 78 | } 79 | 80 | &.codefence > code > span { 81 | display: inline-block; 82 | width: 100%; 83 | } 84 | &.codefence > code > span[class*="numbered"] { 85 | counter-increment: listing; 86 | } 87 | 88 | &.codefence > code > span[class*="numbered"]::before { 89 | content: counter(listing); 90 | display: inline-block; 91 | width: t.$space-12; 92 | padding-left: auto; 93 | padding-right: t.$space-5; 94 | margin-left: auto; 95 | text-align: right; 96 | color: t.$textColor-secondary; 97 | } 98 | } 99 | } 100 | 101 | :export { 102 | themeVars: t.json-stringify($themeVars); 103 | } 104 | ``` -------------------------------------------------------------------------------- /packages/xmlui-website-blocks/src/Carousel/Carousel.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "xmlui/themes.scss" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: ( 5 | ); 6 | 7 | @function createThemeVar($componentVariable) { 8 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 9 | @return t.getThemeVar($themeVars, $componentVariable); 10 | } 11 | 12 | $component: "Carousel"; 13 | 14 | @layer components { 15 | .carousel { 16 | position: relative; 17 | overflow: hidden; 18 | width: createThemeVar("width-#{$component}"); 19 | height: createThemeVar("height-#{$component}"); 20 | justify-content: center; 21 | align-items: center; 22 | } 23 | 24 | .innerWrapper { 25 | display: flex; 26 | width: 100%; 27 | height: 100%; 28 | flex-direction: row; 29 | justify-content: center; 30 | } 31 | 32 | .carouselContentWrapper { 33 | overflow: hidden; 34 | height: 100%; 35 | } 36 | 37 | .carouselContent { 38 | display: flex; 39 | height: 100%; 40 | } 41 | 42 | .vertical { 43 | flex-direction: column; 44 | } 45 | 46 | .carouselItem { 47 | overflow: hidden; 48 | min-width: 0; 49 | flex-shrink: 0; 50 | flex-grow: 0; 51 | flex-basis: 100%; 52 | } 53 | 54 | .controls { 55 | z-index: 99; 56 | display: flex; 57 | right: 1rem; 58 | bottom: 1rem; 59 | position: absolute; 60 | gap: .6rem; 61 | } 62 | 63 | .controlButton { 64 | transition: all .3s; 65 | justify-content: center; 66 | align-items: center; 67 | display: inline-flex; 68 | height: createThemeVar("height-control-#{$component}"); 69 | width: createThemeVar("width-control-#{$component}"); 70 | color: createThemeVar("textColor-control-#{$component}"); 71 | background-color: createThemeVar("backgroundColor-control-#{$component}"); 72 | ; 73 | border-radius: createThemeVar("borderRadius-control-#{$component}"); 74 | } 75 | 76 | .controlButton:hover { 77 | background-color: createThemeVar("backgroundColor-control-hover-#{$component}"); 78 | color: createThemeVar("textColor-control-hover-#{$component}"); 79 | } 80 | 81 | .controlButton:active { 82 | background-color: createThemeVar("backgroundColor-control-active-#{$component}"); 83 | color: createThemeVar("textColor-control-active-#{$component}"); 84 | } 85 | 86 | .controlButton:disabled { 87 | color: createThemeVar("textColor-control-disabled-#{$component}"); 88 | background-color: createThemeVar("backgroundColor-control-disabled-#{$component}"); 89 | } 90 | 91 | .indicators { 92 | position: absolute; 93 | bottom: 1rem; 94 | left: 50%; 95 | transform: translateX(-50%); 96 | } 97 | 98 | .indicator { 99 | transition: all .3s; 100 | width: createThemeVar("width-indicator-#{$component}"); 101 | height: createThemeVar("height-indicator-#{$component}"); 102 | border-radius: 2px; 103 | margin: 0 1px; 104 | color: createThemeVar("textColor-indicator-#{$component}"); 105 | background-color: createThemeVar("backgroundColor-indicator-#{$component}"); 106 | 107 | &:hover { 108 | background-color: createThemeVar("backgroundColor-indicator-hover-#{$component}"); 109 | color: createThemeVar("textColor-indicator-hover-#{$component}"); 110 | } 111 | 112 | &.active { 113 | background-color: createThemeVar("backgroundColor-indicator-active-#{$component}"); 114 | color: createThemeVar("textColor-indicator-active-#{$component}"); 115 | } 116 | } 117 | } 118 | 119 | 120 | // --- We export the theme variables to add them to the component renderer 121 | :export { 122 | themeVars: t.json-stringify($themeVars); 123 | } ``` -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "$schema": "https://turborepo.com/schema.json", 3 | "globalEnv": ["CI"], 4 | "ui": "tui", 5 | "envMode": "loose", 6 | "tasks": { 7 | "build:bin": { 8 | "outputs": ["dist/scripts/**"] 9 | }, 10 | "build:xmlui": { 11 | "outputs": ["dist/lib/**"] 12 | }, 13 | "build:xmlui-standalone": { 14 | "outputs": ["dist/standalone/**"] 15 | }, 16 | "build:xmlui-metadata": { 17 | "inputs": ["$TURBO_DEFAULT$", "!src/language-server/xmlui-metadata-generated.mjs"], 18 | "outputs": ["dist/metadata/**"] 19 | }, 20 | "gen:langserver-metadata": { 21 | "dependsOn": ["build:xmlui-metadata"], 22 | "outputs": ["src/language-server/xmlui-metadata-generated.mjs"] 23 | }, 24 | "build:extension": { 25 | "dependsOn": ["^build:extension"], 26 | "outputs": ["dist/**"] 27 | }, 28 | "build:meta": { 29 | "outputs": ["dist/**"] 30 | }, 31 | "xmlui-vscode#build:vsix": { 32 | "dependsOn": ["xmlui-vscode#build"], 33 | "outputs": ["xmlui-vscode-*.vsix"] 34 | }, 35 | "xmlui-vscode#build": { 36 | "dependsOn": ["^gen:langserver-metadata"], 37 | "outputs": ["dist/**"] 38 | }, 39 | "build:xmlui-all": { 40 | "dependsOn": [ 41 | "build:bin", 42 | "build:xmlui-metadata", 43 | "build:xmlui", 44 | "build:xmlui-standalone", 45 | "build:extension" 46 | ], 47 | "outputs": ["dist/**"] 48 | }, 49 | "gen:releases": { 50 | "env": ["GITHUB_TOKEN", "DOCS_XMLUI_MAX_RELEASES_LENGTH"], 51 | "outputs": ["public/resources/files/releases.json"] 52 | }, 53 | "gen:download-latest-xmlui-release": { 54 | "outputs": ["public/resources/files/for-download/xmlui/xmlui-standalone.umd.js"] 55 | }, 56 | "generate-docs": { 57 | "outputs": ["scripts/generate-docs/metadata/**", "$TURBO_ROOT$/docs/content/**"], 58 | "inputs": ["$TURBO_DEFAULT$"], 59 | "dependsOn": ["build:xmlui-metadata", "build:meta"] 60 | }, 61 | "generate-docs-summaries": { 62 | "outputs": ["$TURBO_ROOT$/docs/content/**"], 63 | "inputs": ["$TURBO_DEFAULT$", "dist/metadata/**"], 64 | "dependsOn": ["generate-docs"] 65 | }, 66 | "build:docs": { 67 | "dependsOn": [ 68 | "^build:extension", 69 | "^build:xmlui", 70 | "gen:releases", 71 | "gen:download-latest-xmlui-release", 72 | "generate-docs-summaries" 73 | ], 74 | "outputs": ["dist/**"] 75 | }, 76 | "build:blog": { 77 | "dependsOn": [ 78 | "^build:extension", 79 | "^build:xmlui" 80 | ], 81 | "outputs": ["dist/**"] 82 | }, 83 | "build:xmlui-test-bed": { 84 | "dependsOn": ["build:extension"], 85 | "outputs": ["src/testing/infrastructure/dist/**"] 86 | }, 87 | "build:test-bed": { 88 | "dependsOn": ["build:extension"], 89 | "outputs": ["dist/**"] 90 | }, 91 | "test:unit": {}, 92 | "test:e2e-smoke": { 93 | "dependsOn": ["build:test-bed", "build:xmlui-test-bed"], 94 | "outputs": ["playwright-report/**"] 95 | }, 96 | "test:e2e-non-smoke": { 97 | "dependsOn": ["build:test-bed", "build:xmlui-test-bed", "test:e2e-smoke"], 98 | "outputs": ["playwright-report/**"] 99 | }, 100 | "test:xmlui-all": { 101 | "dependsOn": ["test:unit", "test:e2e-non-smoke"], 102 | "outputs": ["playwright-report/**"] 103 | }, 104 | "test:xmlui-smoke": { 105 | "dependsOn": ["test:unit", "test:e2e-smoke"], 106 | "outputs": ["playwright-report/**"] 107 | } 108 | } 109 | } 110 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Splitter/Splitter.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | $component: "Splitter"; 11 | $boxShadow-Splitter: createThemeVar("boxShadow-#{$component}"); 12 | $backgroundColor-Splitter: createThemeVar("backgroundColor-#{$component}"); 13 | $borderRadius-Splitter: createThemeVar("borderRadius-#{$component}"); 14 | $borderColor-Splitter: createThemeVar("borderColor-#{$component}"); 15 | $borderWidth-Splitter: createThemeVar("borderWidth-#{$component}"); 16 | $borderStyle-Splitter: createThemeVar("borderStyle-#{$component}"); 17 | $border-Splitter: createThemeVar("border-#{$component}"); 18 | $backgroundColor-resizer-Splitter: createThemeVar("backgroundColor-resizer-#{$component}"); 19 | $thickness-resizer-Splitter: createThemeVar("thickness-resizer-#{$component}"); 20 | $cursor-resizer-horizontal-Splitter: createThemeVar("cursor-resizer-horizontal-#{$component}"); 21 | $cursor-resizer-vertical-Splitter: createThemeVar("cursor-resizer-vertical-#{$component}"); 22 | 23 | @layer components { 24 | .splitter { 25 | overflow: hidden; 26 | position: relative; 27 | flex-wrap: nowrap; 28 | display: flex; 29 | height: 100%; 30 | width: 100%; 31 | box-shadow: $boxShadow-Splitter; 32 | background-color: $backgroundColor-Splitter; 33 | border-radius: $borderRadius-Splitter; 34 | border: $border-Splitter $borderStyle-Splitter $borderColor-Splitter; 35 | user-select: none; 36 | 37 | &.vertical { 38 | flex-direction: column; 39 | } 40 | 41 | &.horizontal { 42 | flex-direction: row; 43 | } 44 | } 45 | 46 | .panel { 47 | overflow: hidden; 48 | display: flex; 49 | flex-direction: column; 50 | flex: 1; 51 | } 52 | 53 | .primaryPanel { 54 | flex-shrink: 0; 55 | flex-grow: 0; 56 | overflow: auto; 57 | order: 1; 58 | } 59 | 60 | .secondaryPanel { 61 | flex: 1; 62 | overflow: auto; 63 | order: 3; 64 | } 65 | 66 | .resizer { 67 | order: 2; 68 | overflow: hidden; 69 | background-color: t.useVar($backgroundColor-resizer-Splitter, transparent); 70 | 71 | &.vertical { 72 | cursor: t.useVar($cursor-resizer-vertical-Splitter, ns-resize); 73 | height: t.useVar($thickness-resizer-Splitter, 5px); 74 | } 75 | 76 | &.horizontal { 77 | cursor: t.useVar($cursor-resizer-horizontal-Splitter, ew-resize); 78 | width: t.useVar($thickness-resizer-Splitter, 5px); 79 | } 80 | } 81 | 82 | .floatingResizer { 83 | position: absolute; 84 | z-index: 1000; 85 | opacity: 0; 86 | overflow: hidden; 87 | transition: opacity 0.2s; 88 | background-color: t.useVar($backgroundColor-resizer-Splitter, transparent); 89 | 90 | &.vertical { 91 | cursor: t.useVar($cursor-resizer-vertical-Splitter, ns-resize); 92 | width: 100%; 93 | height: t.useVar($thickness-resizer-Splitter, 5px); 94 | transform: translateY(-50%); 95 | } 96 | 97 | &.horizontal { 98 | cursor: t.useVar($cursor-resizer-horizontal-Splitter, ew-resize); 99 | width: t.useVar($thickness-resizer-Splitter, 5px); 100 | height: 100%; 101 | transform: translateX(-50%); 102 | } 103 | } 104 | } 105 | 106 | // --- We export the theme variables to add them to the component renderer 107 | :export { 108 | themeVars: t.json-stringify($themeVars); 109 | } 110 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/Carousel/Carousel.module.scss: -------------------------------------------------------------------------------- ```scss 1 | @use "../../components-core/theming/themes" as t; 2 | 3 | // --- This code snippet is required to collect the theme variables used in this module 4 | $themeVars: (); 5 | @function createThemeVar($componentVariable) { 6 | $themeVars: t.appendThemeVar($themeVars, $componentVariable) !global; 7 | @return t.getThemeVar($themeVars, $componentVariable); 8 | } 9 | 10 | $component: "Carousel"; 11 | 12 | @layer components { 13 | .carousel { 14 | position: relative; 15 | overflow: hidden; 16 | width: createThemeVar("width-#{$component}"); 17 | height: createThemeVar("height-#{$component}"); 18 | justify-content: center; 19 | align-items: center; 20 | } 21 | 22 | .innerWrapper { 23 | display: flex; 24 | width: 100%; 25 | height: 100%; 26 | flex-direction: row; 27 | justify-content: center; 28 | } 29 | 30 | .carouselContentWrapper { 31 | overflow: hidden; 32 | height: 100%; 33 | } 34 | 35 | .carouselContent { 36 | display: flex; 37 | height: 100%; 38 | } 39 | 40 | .vertical { 41 | flex-direction: column; 42 | } 43 | 44 | .carouselItem { 45 | overflow: hidden; 46 | min-width: 0; 47 | flex-shrink: 0; 48 | flex-grow: 0; 49 | flex-basis: 100%; 50 | } 51 | 52 | .controls { 53 | z-index: 99; 54 | display: flex; 55 | right: 1rem; 56 | bottom: 1rem; 57 | position: absolute; 58 | gap: .6rem; 59 | } 60 | 61 | .controlButton { 62 | transition: all .3s; 63 | justify-content: center; 64 | align-items: center; 65 | display: inline-flex; 66 | height: createThemeVar("height-control-#{$component}"); 67 | width: createThemeVar("width-control-#{$component}"); 68 | color: createThemeVar("textColor-control-#{$component}"); 69 | background-color: createThemeVar("backgroundColor-control-#{$component}");; 70 | border-radius: createThemeVar("borderRadius-control-#{$component}"); 71 | } 72 | 73 | .controlButton:hover { 74 | background-color: createThemeVar("backgroundColor-control-hover-#{$component}"); 75 | color: createThemeVar("textColor-control-hover-#{$component}"); 76 | } 77 | 78 | .controlButton:active { 79 | background-color: createThemeVar("backgroundColor-control-active-#{$component}"); 80 | color: createThemeVar("textColor-control-active-#{$component}"); 81 | } 82 | 83 | .controlButton:disabled { 84 | color: createThemeVar("textColor-control-disabled-#{$component}"); 85 | background-color: createThemeVar("backgroundColor-control-disabled-#{$component}"); 86 | } 87 | 88 | .indicators { 89 | position: absolute; 90 | bottom: 1rem; 91 | left: 50%; 92 | transform: translateX(-50%); 93 | } 94 | 95 | .indicator { 96 | transition: all .3s; 97 | width: createThemeVar("width-indicator-#{$component}"); 98 | height: createThemeVar("height-indicator-#{$component}"); 99 | border-radius: 2px; 100 | margin: 0 1px; 101 | color: createThemeVar("textColor-indicator-#{$component}"); 102 | background-color: createThemeVar("backgroundColor-indicator-#{$component}"); 103 | 104 | &:hover { 105 | background-color: createThemeVar("backgroundColor-indicator-hover-#{$component}"); 106 | color: createThemeVar("textColor-indicator-hover-#{$component}"); 107 | } 108 | 109 | &.active { 110 | background-color: createThemeVar("backgroundColor-indicator-active-#{$component}"); 111 | color: createThemeVar("textColor-indicator-active-#{$component}"); 112 | } 113 | } 114 | } 115 | 116 | 117 | // --- We export the theme variables to add them to the component renderer 118 | :export { 119 | themeVars: t.json-stringify($themeVars); 120 | } 121 | 122 | 123 | 124 | ``` -------------------------------------------------------------------------------- /xmlui/tests/parsers/scripting/eval-tree-pre-post.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | 3 | import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync"; 4 | import { createEvalContext } from "./test-helpers"; 5 | 6 | describe("Evaluate prefix/postfix expressions", () => { 7 | const prePostCases1 = [ 8 | { src: "++x", ctx: { x: 0 }, exp: 1, varExp: 1 }, 9 | { src: "--x", ctx: { x: 0 }, exp: -1, varExp: -1 }, 10 | { src: "x++", ctx: { x: 0 }, exp: 0, varExp: 1 }, 11 | { src: "x--", ctx: { x: 0 }, exp: 0, varExp: -1 } 12 | ]; 13 | prePostCases1.forEach(c => { 14 | it(`Prefix/postfix '${c.src}'`, () => { 15 | // --- Arrange 16 | const context = createEvalContext({ 17 | localContext: c.ctx 18 | }); 19 | 20 | // --- Act 21 | const value = evalBindingExpression(c.src, context); 22 | 23 | // --- Arrange 24 | expect(value).equal(c.exp); 25 | expect(context.localContext["x"]).equal(c.varExp); 26 | }); 27 | }); 28 | const prePostCases2 = [ 29 | { src: "++j[2]", exp: 4, varExp: 4 }, 30 | { src: "--j[2]", exp: 2, varExp: 2 }, 31 | { src: "j[1+1]++", exp: 3, varExp: 4 }, 32 | { src: "j[4-2]--", exp: 3, varExp: 2 } 33 | ]; 34 | prePostCases2.forEach(c => { 35 | it(`Prefix/postfix '${c.src}'`, () => { 36 | // --- Arrange 37 | const jArr = [1, 2, 3]; 38 | const context = createEvalContext({ 39 | localContext: { 40 | j: jArr 41 | } 42 | }); 43 | 44 | // --- Act 45 | const value = evalBindingExpression(c.src, context); 46 | 47 | // --- Arrange 48 | expect(value).equal(c.exp); 49 | expect(context.localContext.j[2]).equal(c.varExp); 50 | }); 51 | }); 52 | 53 | const prePostCases3 = [ 54 | { src: "++j[2].h", exp: 4, varExp: 4 }, 55 | { src: "--j[2].h", exp: 2, varExp: 2 }, 56 | { src: "j[1+1].h++", exp: 3, varExp: 4 }, 57 | { src: "j[4-2].h--", exp: 3, varExp: 2 } 58 | ]; 59 | prePostCases3.forEach(c => { 60 | it(`Prefix/postfix '${c.src}'`, () => { 61 | // --- Arrange 62 | const jArr = [{ h: 1 }, { h: 2 }, { h: 3 }]; 63 | const context = createEvalContext({ 64 | localContext: { 65 | j: jArr 66 | } 67 | }); 68 | 69 | // --- Act 70 | const value = evalBindingExpression(c.src, context); 71 | 72 | // --- Arrange 73 | expect(value).equal(c.exp); 74 | expect(context.localContext.j[2].h).equal(c.varExp); 75 | }); 76 | }); 77 | 78 | const prePostCases4 = [ 79 | { src: "++j.h[2]", exp: 4, varExp: 4 }, 80 | { src: "--j.h[2]", exp: 2, varExp: 2 }, 81 | { src: "j.h[1+1]++", exp: 3, varExp: 4 }, 82 | { src: "j.h[4-2]--", exp: 3, varExp: 2 } 83 | ]; 84 | prePostCases4.forEach(c => { 85 | it(`Prefix/postfix '${c.src}'`, () => { 86 | // --- Arrange 87 | const jArr = [1, 2, 3]; 88 | const context = createEvalContext({ 89 | localContext: { 90 | j: { h: jArr } 91 | } 92 | }); 93 | 94 | // --- Act 95 | const value = evalBindingExpression(c.src, context); 96 | 97 | // --- Arrange 98 | expect(value).equal(c.exp); 99 | expect(context.localContext.j.h[2]).equal(c.varExp); 100 | }); 101 | }); 102 | 103 | it("Postfix with arrow", () => { 104 | // --- Arrange 105 | const source = "(x => ++x)(count)"; 106 | const context = createEvalContext({ 107 | localContext: { 108 | count: 6 109 | } 110 | }); 111 | 112 | // --- Act 113 | const value = evalBindingExpression(source, context); 114 | 115 | // --- Arrange 116 | expect(value).equal(7); 117 | }); 118 | }); 119 | ``` -------------------------------------------------------------------------------- /packages/xmlui-animations/src/SlideInAnimation.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import { createComponentRenderer, createMetadata } from "xmlui"; 2 | import { Animation, defaultProps } from "./AnimationNative"; 3 | 4 | const COMP = "SlideInAnimation"; 5 | 6 | const defaultAnimationValues = { 7 | direction: "left", 8 | }; 9 | 10 | export const SlideInAnimationMd = createMetadata({ 11 | status: "experimental", 12 | description: `The \`${COMP}\` component represents an animation that slides in the content from the left.`, 13 | docFolder: "src", 14 | props: { 15 | direction: { 16 | description: "The direction of the animation.", 17 | valueType: "string", 18 | options: ["left", "right", "top", "bottom"], 19 | defaultValue: defaultAnimationValues.direction, 20 | }, 21 | duration: { 22 | description: "The duration of the animation in milliseconds.", 23 | valueType: "number", 24 | }, 25 | animateWhenInView: { 26 | description: "Indicates whether the animation should start when the component is in view.", 27 | valueType: "boolean", 28 | }, 29 | reverse: { 30 | description: `Indicates whether the animation should run in reverse`, 31 | defaultValue: defaultProps.reverse, 32 | valueType: "boolean", 33 | }, 34 | loop: { 35 | description: `Indicates whether the animation should loop`, 36 | defaultValue: defaultProps.loop, 37 | valueType: "boolean", 38 | }, 39 | delay: { 40 | description: `The delay before the animation starts in milliseconds`, 41 | defaultValue: defaultProps.delay, 42 | valueType: "number", 43 | }, 44 | }, 45 | events: { 46 | started: { description: `Event fired when the animation starts` }, 47 | stopped: { description: `Event fired when the animation stops` }, 48 | }, 49 | apis: { 50 | start: { description: `Starts the animation` }, 51 | stop: { description: `Stops the animation` }, 52 | }, 53 | }); 54 | 55 | export const slideInAnimationRenderer = createComponentRenderer( 56 | "SlideInAnimation", 57 | SlideInAnimationMd, 58 | ({ node, renderChild, extractValue, registerComponentApi, lookupEventHandler }) => { 59 | const direction = extractValue.asOptionalString(node.props.direction, defaultAnimationValues.direction); 60 | const animation = { 61 | from: { 62 | transform: 63 | direction === "right" 64 | ? "translateX(100%)" 65 | : direction === "left" 66 | ? "translateX(-100%)" 67 | : direction === "top" 68 | ? "translateY(-100%)" 69 | : "translateY(100%)", 70 | }, 71 | to: { 72 | transform: 73 | direction === "right" || direction === "left" ? "translateX(0)" : "translateY(0)", 74 | }, 75 | }; 76 | return ( 77 | <Animation 78 | registerComponentApi={registerComponentApi} 79 | animation={animation} 80 | duration={extractValue.asOptionalNumber(node.props.duration)} 81 | onStop={lookupEventHandler("stopped")} 82 | onStart={lookupEventHandler("started")} 83 | animateWhenInView={extractValue.asOptionalBoolean(node.props.animateWhenInView)} 84 | reverse={extractValue.asOptionalBoolean(node.props.reverse)} 85 | loop={extractValue.asOptionalBoolean(node.props.loop)} 86 | delay={extractValue.asOptionalNumber(node.props.delay)} 87 | > 88 | {renderChild(node.children)} 89 | </Animation> 90 | ); 91 | }, 92 | ); 93 | ``` -------------------------------------------------------------------------------- /xmlui/tests/components-core/scripts-runner/eval-tree-pre-post.test.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { describe, expect, it } from "vitest"; 2 | 3 | import { evalBindingExpression } from "../../../src/components-core/script-runner/eval-tree-sync"; 4 | import { createEvalContext } from "./test-helpers"; 5 | 6 | describe("Evaluate prefix/postfix expressions (exp)", () => { 7 | const prePostCases1 = [ 8 | { src: "++x", ctx: { x: 0 }, exp: 1, varExp: 1 }, 9 | { src: "--x", ctx: { x: 0 }, exp: -1, varExp: -1 }, 10 | { src: "x++", ctx: { x: 0 }, exp: 0, varExp: 1 }, 11 | { src: "x--", ctx: { x: 0 }, exp: 0, varExp: -1 } 12 | ]; 13 | prePostCases1.forEach(c => { 14 | it(`Prefix/postfix '${c.src}'`, () => { 15 | // --- Arrange 16 | const context = createEvalContext({ 17 | localContext: c.ctx 18 | }); 19 | 20 | // --- Act 21 | const value = evalBindingExpression(c.src, context); 22 | 23 | // --- Arrange 24 | expect(value).equal(c.exp); 25 | expect(context.localContext["x"]).equal(c.varExp); 26 | }); 27 | }); 28 | const prePostCases2 = [ 29 | { src: "++j[2]", exp: 4, varExp: 4 }, 30 | { src: "--j[2]", exp: 2, varExp: 2 }, 31 | { src: "j[1+1]++", exp: 3, varExp: 4 }, 32 | { src: "j[4-2]--", exp: 3, varExp: 2 } 33 | ]; 34 | prePostCases2.forEach(c => { 35 | it(`Prefix/postfix '${c.src}'`, () => { 36 | // --- Arrange 37 | const jArr = [1, 2, 3]; 38 | const context = createEvalContext({ 39 | localContext: { 40 | j: jArr 41 | } 42 | }); 43 | 44 | // --- Act 45 | const value = evalBindingExpression(c.src, context); 46 | 47 | // --- Arrange 48 | expect(value).equal(c.exp); 49 | expect(context.localContext.j[2]).equal(c.varExp); 50 | }); 51 | }); 52 | 53 | const prePostCases3 = [ 54 | { src: "++j[2].h", exp: 4, varExp: 4 }, 55 | { src: "--j[2].h", exp: 2, varExp: 2 }, 56 | { src: "j[1+1].h++", exp: 3, varExp: 4 }, 57 | { src: "j[4-2].h--", exp: 3, varExp: 2 } 58 | ]; 59 | prePostCases3.forEach(c => { 60 | it(`Prefix/postfix '${c.src}'`, () => { 61 | // --- Arrange 62 | const jArr = [{ h: 1 }, { h: 2 }, { h: 3 }]; 63 | const context = createEvalContext({ 64 | localContext: { 65 | j: jArr 66 | } 67 | }); 68 | 69 | // --- Act 70 | const value = evalBindingExpression(c.src, context); 71 | 72 | // --- Arrange 73 | expect(value).equal(c.exp); 74 | expect(context.localContext.j[2].h).equal(c.varExp); 75 | }); 76 | }); 77 | 78 | const prePostCases4 = [ 79 | { src: "++j.h[2]", exp: 4, varExp: 4 }, 80 | { src: "--j.h[2]", exp: 2, varExp: 2 }, 81 | { src: "j.h[1+1]++", exp: 3, varExp: 4 }, 82 | { src: "j.h[4-2]--", exp: 3, varExp: 2 } 83 | ]; 84 | prePostCases4.forEach(c => { 85 | it(`Prefix/postfix '${c.src}'`, () => { 86 | // --- Arrange 87 | const jArr = [1, 2, 3]; 88 | const context = createEvalContext({ 89 | localContext: { 90 | j: { h: jArr } 91 | } 92 | }); 93 | 94 | // --- Act 95 | const value = evalBindingExpression(c.src, context); 96 | 97 | // --- Arrange 98 | expect(value).equal(c.exp); 99 | expect(context.localContext.j.h[2]).equal(c.varExp); 100 | }); 101 | }); 102 | 103 | it("Postfix with arrow", () => { 104 | // --- Arrange 105 | const source = "(x => ++x)(count)"; 106 | const context = createEvalContext({ 107 | localContext: { 108 | count: 6 109 | } 110 | }); 111 | 112 | // --- Act 113 | const value = evalBindingExpression(source, context); 114 | 115 | // --- Arrange 116 | expect(value).equal(7); 117 | }); 118 | }); 119 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/script-runner/statement-queue.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { Statement } from "./ScriptingSourceTree"; 2 | 3 | export type StatementRunTimeInfo = { 4 | guard?: boolean; 5 | keys?: string[]; 6 | keyIndex?: number; 7 | iterator?: any; 8 | removeBlockScope?: boolean; 9 | }; 10 | 11 | export type StatementWithInfo = { 12 | // --- The description of the statement to execute 13 | statement: Statement; 14 | 15 | // --- Execution information for the statement 16 | execInfo?: StatementRunTimeInfo; 17 | }; 18 | 19 | // An item of the queue we use for processing a statement 20 | export type StatementQueueItem = StatementWithInfo & { 21 | // --- The label of the item. We need this information for "break" and "continue" statements 22 | label: number; 23 | }; 24 | 25 | // The outcome of processing a statement of the queue 26 | export type ProcessOutcome = { 27 | // --- New statements to unshift into the queue (optional) 28 | toUnshift?: StatementQueueItem[]; 29 | 30 | // --- Label from which statements should be preserved in the queue 31 | clearToLabel?: number; 32 | }; 33 | 34 | // --- Implements the statement queue 35 | export class StatementQueue { 36 | private items: StatementQueueItem[] = []; 37 | 38 | // --- The current queue length 39 | get length(): number { 40 | return this.items.length; 41 | } 42 | 43 | // --- Get the subsequent item in the queue without dequeuing it 44 | peek(): StatementQueueItem | undefined { 45 | return this.items[0]; 46 | } 47 | 48 | // --- Remove and return the subsequent item from the queue 49 | dequeue(): StatementQueueItem | undefined { 50 | return this.items.shift(); 51 | } 52 | 53 | // --- Push the specified items into the queue and return the corresponding queue items 54 | push(statements: StatementQueueItem[]): void { 55 | this.items.push(...statements); 56 | } 57 | 58 | // --- Unshift the specified items into the queue in reverse order (starting from the last action) and 59 | // --- return the corresponding queue items 60 | unshift(statements: StatementQueueItem[]): void { 61 | this.items.unshift(...statements); 62 | } 63 | 64 | // --- Remove all items starting from the beginning of the queue, while not reaching the item with the 65 | // --- specified label. If the label is found, that remains in the queue; otherwise the queue is emptied 66 | clearToLabel(label: number): void { 67 | while (this.items.length > 0 && this.items[0].label !== label) { 68 | this.items.shift(); 69 | } 70 | } 71 | } 72 | 73 | // --- Queue information for test and diagnostics purposes 74 | export type QueueInfo = { 75 | processedStatements: number; 76 | maxQueueLength: number; 77 | unshiftedItems: number; 78 | clearToLabels: number; 79 | maxBlocks: number; 80 | maxLoops: number; 81 | }; 82 | 83 | // --- The ID of the last used label 84 | let nextLabelValue = 1; 85 | 86 | // --- Map a single item to a queue statement list 87 | export function mapToItem(statement: Statement): StatementQueueItem[] { 88 | return [ 89 | { 90 | label: nextLabelValue++, 91 | statement: { ...statement }, 92 | } as StatementQueueItem, 93 | ]; 94 | } 95 | 96 | // --- Map a single statement into a queue item 97 | function mapStatementToQueueItem(stmt: StatementWithInfo): StatementQueueItem { 98 | return { 99 | label: nextLabelValue++, 100 | statement: stmt.statement, 101 | execInfo: { ...stmt.execInfo }, 102 | }; 103 | } 104 | 105 | // --- Map a list of statements into queue items 106 | export function mapStatementsToQueueItems(statements: StatementWithInfo[]): StatementQueueItem[] { 107 | return statements.map(mapStatementToQueueItem); 108 | } 109 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/interception/abstractions.ts: -------------------------------------------------------------------------------- ```typescript 1 | import type { DelayMode } from "msw"; 2 | 3 | export type AuthDefinition = { 4 | defaultLoggedInUser?: any 5 | }; 6 | 7 | export type TableDescriptor = { 8 | name: string, 9 | fields?: Record<string, any>, 10 | pk: Array<string>; 11 | indexes?: Array<string>; 12 | } 13 | 14 | export type SchemaDescriptor = { 15 | tables: Array<TableDescriptor>, 16 | relationships?: any; 17 | dtos?: any; 18 | } 19 | 20 | // Represents an interceptor between API consumers (UI in the browser) and their backend 21 | export type ApiInterceptorDefinition = { 22 | // Type of backend interceptor 23 | type?: string; 24 | 25 | // Interceptor configuration 26 | config?: Record<string, any>; 27 | 28 | artificialDelay?: number | DelayMode; 29 | 30 | // Information for code generators 31 | schemaDescriptor?: SchemaDescriptor 32 | 33 | // URL to particular interceptor is bound to 34 | apiUrl?: string; 35 | 36 | // Initial data to set up (initialize) the interceptor 37 | initialData?: Record<string, any[]> | (() => Promise<Record<string, any[]>>); 38 | 39 | helpers?: Record<string, any>; 40 | initialize?: string; 41 | 42 | // Available operations 43 | operations?: Record<string, InterceptorOperationDef>; 44 | 45 | auth?: AuthDefinition; 46 | 47 | useWorker?: boolean; 48 | }; 49 | 50 | // Represents a single operation of the interceptor 51 | export type InterceptorOperationDef = { 52 | // Operation method (can be extended in the future) 53 | method: "get" | "post" | "put" | "delete" | "patch" | "head" | "options"; 54 | 55 | // Operation URL(s) 56 | url: string | Array<string>; 57 | 58 | // The source code of the operation's handler. This code is parsed and executed 59 | handler: string; 60 | 61 | // Shape of the request this operation expects 62 | requestShape?: any; 63 | 64 | // Shape of the response this operation returns 65 | responseShape?: any; 66 | 67 | // Type mappings of path parameters 68 | pathParamTypes?: Record<string, string> 69 | 70 | // Type mappings of query parameters 71 | queryParamTypes?: Record<string, string>; 72 | 73 | successStatusCode?: number; 74 | }; 75 | 76 | // Describes the backend environment (change it for mocked tests) 77 | export type BackendEnvironment = { 78 | getDate: () => Date; 79 | }; 80 | 81 | // Represents the persistence service (database) of the backend 82 | // TODO: Review this interface 83 | export interface IDatabase { 84 | [key: string]: unknown; 85 | getItems(collectionName: string): Promise<Array<any>>; 86 | getItem(collectionName: string, predicate: (item: any) => Promise<boolean>): Promise<any>; 87 | getItemById(collectionName: string, id: any): Promise<any>; 88 | deleteItems(collectionName: string, predicate: (item: any) => Promise<boolean>): Promise<void>; 89 | insertItem(collectionName: string, item: any): Promise<any>; 90 | updateItem(collectionName: string, item: any): Promise<any>; 91 | updateItem(collectionName: string, item: any): Promise<any>; 92 | } 93 | 94 | // Represents the request parameters the backed translates to operations 95 | export type RequestParams = { 96 | // URL path parameters 97 | pathParams: Record<string, any>; 98 | 99 | // URL query parameters 100 | queryParams: Record<string, any>; 101 | 102 | // The body of a request 103 | requestBody: any; 104 | 105 | cookies: any; 106 | 107 | requestHeaders: Record<string, string>; 108 | }; 109 | 110 | // Represents the definition of a particular backed 111 | export type BackendDefinition = { 112 | initialData?: Record<string, any[]>; 113 | initialize?: string; 114 | helpers?: Record<string, any>; 115 | operations: Record<string, string>; 116 | }; 117 | ``` -------------------------------------------------------------------------------- /docs/public/pages/howto/sync-selections-between-table-and-list-views.md: -------------------------------------------------------------------------------- ```markdown 1 | # Sync selections between a Table view and a List view 2 | 3 | The Table view syncs selections automatically via `syncWithAppState`, while the Tiles (List) view uses checkboxes that manually coordinate with AppState. 4 | 5 | ```xmlui-pg 6 | ---app 7 | <App> 8 | <Files /> 9 | </App> 10 | ---api 11 | { 12 | "apiUrl": "/api", 13 | "initialize": "$state.files = [ 14 | { id: 1, name: 'document.pdf', size: '2.3 MB', type: 'PDF' }, 15 | { id: 2, name: 'image.jpg', size: '1.1 MB', type: 'Image' }, 16 | { id: 3, name: 'spreadsheet.xlsx', size: '856 KB', type: 'Excel' }, 17 | { id: 4, name: 'presentation.pptx', size: '4.2 MB', type: 'PowerPoint' }, 18 | { id: 5, name: 'text.txt', size: '12 KB', type: 'Text' } 19 | ]", 20 | "operations": { 21 | "get-files": { 22 | "url": "/files", 23 | "method": "get", 24 | "handler": "return $state.files" 25 | } 26 | } 27 | } 28 | ---comp display /selections/ /sharedSelections/ /selectedIds/ 29 | <Component name="Files"> 30 | <AppState id="selections" bucket="sharedSelections" initialValue="{{ selectedIds: [] }}" /> 31 | <DataSource id="files" url="/api/files" /> 32 | <Text>Selected: {selections.value.selectedIds.length} items</Text> 33 | <Tabs> 34 | <TabItem label="Table View"> 35 | <TableView data="{files}" /> 36 | </TabItem> 37 | <TabItem label="Tiles View"> 38 | <TilesView data="{files}" /> 39 | </TabItem> 40 | <TabItem label="Debug"> 41 | <DebugView /> 42 | </TabItem> 43 | </Tabs> 44 | </Component> 45 | ---comp display /selections/ /sharedSelections/ /selectedIds/ 46 | <Component name="TableView"> 47 | <AppState id="selections" bucket="sharedSelections" /> 48 | <Card> 49 | <Table 50 | data="{$props.data}" 51 | rowsSelectable="true" 52 | syncWithAppState="{selections}" 53 | loading="{!$props.data}" 54 | > 55 | <Column bindTo="name" /> 56 | <Column bindTo="size" /> 57 | <Column bindTo="type" /> 58 | </Table> 59 | </Card> 60 | </Component> 61 | ---comp display /selections/ /sharedSelections/ /selectedIds/ 62 | <Component name="TilesView"> 63 | <AppState id="selections" bucket="sharedSelections" /> 64 | <List data="{$props.data}"> 65 | <Card> 66 | <HStack verticalAlignment="center"> 67 | <Checkbox 68 | initialValue="{selections.value.selectedIds?.includes($item.id) || false}" 69 | onDidChange="(checked) => { 70 | const ids = selections.value.selectedIds || []; 71 | if (checked) { 72 | selections.update({ selectedIds: [...ids, $item.id] }); 73 | } else { 74 | selections.update({ selectedIds: ids.filter(id => id !== $item.id) }); 75 | } 76 | }" 77 | /> 78 | <VStack> 79 | <Text>{$item.name}</Text> 80 | <Text variant="caption">{$item.size} - {$item.type}</Text> 81 | </VStack> 82 | </HStack> 83 | </Card> 84 | </List> 85 | </Component> 86 | ---desc 87 | The spread operator `...ids` takes all existing IDs from the array, and creates a new array with those IDs plus the new one. 88 | 89 | The filter method creates a new array with only items that meet a condition. 90 | ---comp display /selections/ /sharedSelections/ /selectedIds/ 91 | <Component name="DebugView"> 92 | <AppState id="selections" bucket="sharedSelections" /> 93 | <Card> 94 | <VStack> 95 | <Text>selections.value</Text> 96 | <Text variant="code">{JSON.stringify(selections.value, null, 2)}</Text> 97 | </VStack> 98 | </Card> 99 | </Component> 100 | ``` 101 | 102 | 103 | 104 | 105 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/PageMetaTitle/PageMetaTitle.spec.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { expect, test } from "../../testing/fixtures"; 2 | 3 | // ============================================================================= 4 | // BASIC FUNCTIONALITY TESTS 5 | // ============================================================================= 6 | 7 | test.describe("Basic Functionality", () => { 8 | test("component renders with default props", async ({ initTestBed, page }) => { 9 | await initTestBed(`<PageMetaTitle />`); 10 | 11 | // PageMetaTitle doesn't render visible DOM elements, it only affects page title 12 | // Default title should be set in browser with test bed suffix 13 | await expect(page).toHaveTitle("XMLUI Application | test bed app"); 14 | }); 15 | 16 | test("component renders with value property", async ({ initTestBed, page }) => { 17 | await initTestBed(`<PageMetaTitle value="Custom Page Title" />`); 18 | 19 | // Custom title should be set in browser with test bed suffix 20 | await expect(page).toHaveTitle("Custom Page Title | test bed app"); 21 | }); 22 | 23 | test("component handles dynamic value changes", async ({ initTestBed, page }) => { 24 | const { testStateDriver } = await initTestBed(` 25 | <VStack> 26 | <PageMetaTitle value="{testState || 'Initial Title'}" /> 27 | <Button onClick="testState = 'Updated Title'">Update Title</Button> 28 | </VStack> 29 | `); 30 | 31 | // Initial title should be set 32 | await expect(page).toHaveTitle("Initial Title | test bed app"); 33 | 34 | // Click button to update title 35 | await page.getByRole("button").click(); 36 | await expect.poll(testStateDriver.testState).toEqual("Updated Title"); 37 | 38 | // Title should be updated in browser 39 | await expect(page).toHaveTitle("Updated Title | test bed app"); 40 | }); 41 | 42 | test("component handles child content as title", async ({ initTestBed, page }) => { 43 | await initTestBed(` 44 | <PageMetaTitle> 45 | Child Content Title 46 | </PageMetaTitle> 47 | `); 48 | 49 | // Child content should be used as title 50 | await expect(page).toHaveTitle("Child Content Title | test bed app"); 51 | }); 52 | 53 | test("value property takes precedence over child content", async ({ initTestBed, page }) => { 54 | await initTestBed(` 55 | <PageMetaTitle value="Value Property Title"> 56 | Child Content That Should Be Ignored 57 | </PageMetaTitle> 58 | `); 59 | 60 | // Value property should take precedence 61 | await expect(page).toHaveTitle("Value Property Title | test bed app"); 62 | }); 63 | 64 | test("multiple PageMetaTitle components - last one wins", async ({ initTestBed, page }) => { 65 | await initTestBed(` 66 | <VStack> 67 | <PageMetaTitle value="First Title" /> 68 | <PageMetaTitle value="Second Title" /> 69 | <PageMetaTitle value="Final Title" /> 70 | </VStack> 71 | `); 72 | 73 | // Last component should set the final title 74 | await expect(page).toHaveTitle("Final Title | test bed app"); 75 | }); 76 | 77 | test("component updates existing page title", async ({ initTestBed, page }) => { 78 | // Set initial title 79 | await initTestBed(`<PageMetaTitle value="Initial Title" />`); 80 | await expect(page).toHaveTitle("Initial Title | test bed app"); 81 | 82 | // Update with new component 83 | await initTestBed(`<PageMetaTitle value="Updated Title" />`); 84 | await expect(page).toHaveTitle("Updated Title | test bed app"); 85 | }); 86 | }); 87 | ``` -------------------------------------------------------------------------------- /xmlui/src/components-core/action/FileUploadAction.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import type { ActionExecutionContext } from "../../abstractions/ActionDefs"; 2 | import type { ComponentDef } from "../../abstractions/ComponentDefs"; 3 | import { extractParam } from "../utils/extractParam"; 4 | import { invalidateQueries } from "../utils/actionUtils"; 5 | import type { ApiActionOptions, UploadOperationDef } from "../RestApiProxy"; 6 | import RestApiProxy from "../RestApiProxy"; 7 | import { createAction } from "./actions"; 8 | 9 | export interface UploadActionComponent extends ComponentDef { 10 | props: { 11 | invalidates?: string | string[]; 12 | } & UploadOperationDef; 13 | events?: { 14 | error?: string; 15 | success?: string; 16 | }; 17 | } 18 | 19 | export type UploadActionParams = { 20 | invalidates?: string | string[]; 21 | params: any; 22 | chunkSizeInBytes?: number; 23 | onError?: string; 24 | onProgress?: (...args: any) => void; 25 | } & UploadOperationDef; 26 | 27 | async function uploadFile( 28 | { appContext, state, lookupAction, uid }: ActionExecutionContext, 29 | { 30 | params, 31 | invalidates, 32 | onError, 33 | queryParams, 34 | asForm, 35 | file, 36 | headers, 37 | url, 38 | method, 39 | formParams, 40 | rawBody, 41 | body, 42 | chunkSizeInBytes, 43 | onProgress, 44 | }: UploadActionParams, 45 | { resolveBindingExpressions }: ApiActionOptions = {}, 46 | ) { 47 | const stateContext = { ...params, ...state }; 48 | const api = new RestApiProxy(appContext); 49 | 50 | const operation: UploadOperationDef = { 51 | file, 52 | body, 53 | asForm, 54 | formParams, 55 | rawBody, 56 | method, 57 | url, 58 | queryParams, 59 | headers, 60 | }; 61 | 62 | let result = null; 63 | try { 64 | const _chunkSizeInBytes = extractParam(stateContext, chunkSizeInBytes, appContext); 65 | const _onProgress = extractParam(stateContext, onProgress, appContext); 66 | if (_chunkSizeInBytes !== undefined) { 67 | const _file = extractParam(stateContext, file, appContext); 68 | const numberOfChunks = Math.ceil(_file.size / _chunkSizeInBytes); 69 | 70 | for (let i = 0; i < numberOfChunks; i++) { 71 | const start = i * _chunkSizeInBytes; 72 | const chunkEnd = Math.min(start + _chunkSizeInBytes, _file.size); 73 | const chunk = _file.slice(start, chunkEnd); 74 | result = await api.upload({ 75 | operation, 76 | chunk: { 77 | blob: chunk, 78 | chunkStart: start, 79 | chunkEnd: chunkEnd, 80 | }, 81 | params: stateContext, 82 | onUploadProgress: (progressEvent) => { 83 | const overallTotal = _file.size; 84 | const overallLoaded = start + progressEvent.loaded; 85 | const overallProgressEvent = { 86 | total: overallTotal, 87 | loaded: overallLoaded, 88 | progress: overallLoaded / overallTotal, 89 | }; 90 | _onProgress?.(overallProgressEvent); 91 | }, 92 | resolveBindingExpressions, 93 | }); 94 | } 95 | } else { 96 | result = await api.upload({ 97 | operation, 98 | params: stateContext, 99 | onUploadProgress: _onProgress, 100 | resolveBindingExpressions, 101 | }); 102 | } 103 | } catch (e) { 104 | const onErrorFn = lookupAction(onError, uid); 105 | const result = await onErrorFn?.(e, stateContext["$param"]); 106 | if (result !== false) { 107 | throw e; 108 | } 109 | } 110 | void invalidateQueries(invalidates, appContext, state); 111 | return result; 112 | } 113 | 114 | export const uploadAction = createAction("upload", uploadFile); 115 | ``` -------------------------------------------------------------------------------- /xmlui/src/components/FileUploadDropZone/FileUploadDropZone.tsx: -------------------------------------------------------------------------------- ```typescript 1 | import styles from "./FileUploadDropZone.module.scss"; 2 | 3 | import { createComponentRenderer } from "../../components-core/renderers"; 4 | import { parseScssVar } from "../../components-core/theming/themeVars"; 5 | import { FileUploadDropZone, defaultProps } from "./FileUploadDropZoneNative"; 6 | import { createMetadata, d } from "../metadata-helpers"; 7 | 8 | const COMP = "FileUploadDropZone"; 9 | 10 | export const FileUploadDropZoneMd = createMetadata({ 11 | status: "stable", 12 | description: 13 | "`FileUploadDropZone` enables users to upload files by dragging and dropping " + 14 | "files from their local file system onto a designated area within the UI.", 15 | props: { 16 | text: { 17 | description: 18 | "With this property, you can change the default text to display when files " + 19 | "are dragged over the drop zone.", 20 | defaultValue: defaultProps.text, 21 | type: "string", 22 | }, 23 | allowPaste: { 24 | description: 25 | "This property indicates if the drop zone accepts files pasted from the " + 26 | "clipboard (\`true\`) or " + 27 | "only dragged files (\`false\`).", 28 | type: "boolean", 29 | defaultValue: defaultProps.allowPaste, 30 | }, 31 | enabled: d( 32 | `If set to \`false\`, the drop zone will be disabled and users will not be able to upload files.`, 33 | null, 34 | "boolean", 35 | true, 36 | ), 37 | acceptedFileTypes: d(`Accepted file MIME types, separated by commas. For example: 'image/*,application/pdf'.`), 38 | maxFiles: d(`The maximum number of files that can be selected.`), 39 | }, 40 | events: { 41 | upload: d( 42 | `This component accepts files for upload but does not perform the actual operation. It fires ` + 43 | `the \`upload\` event and passes the list files to upload in the method's argument. You can ` + 44 | `use the passed file information to implement the upload (according to the protocol your ` + 45 | `backend supports).`, 46 | ), 47 | }, 48 | themeVars: parseScssVar(styles.themeVars), 49 | defaultThemeVars: { 50 | "backgroundColor-FileUploadDropZone": "$backgroundColor", 51 | "backgroundColor-dropping-FileUploadDropZone": "$backgroundColor--selected", 52 | "opacity-dropping-FileUploadDropZone": "0.5", 53 | "textColor-FileUploadDropZone": "$textColor", 54 | light: { 55 | // --- No light-specific theme vars 56 | }, 57 | dark: { 58 | // --- No dark-specific theme vars 59 | }, 60 | }, 61 | }); 62 | 63 | export const fileUploadDropZoneComponentRenderer = createComponentRenderer( 64 | COMP, 65 | FileUploadDropZoneMd, 66 | ({ node, extractValue, updateState, renderChild, lookupEventHandler, registerComponentApi, className }) => { 67 | return ( 68 | <FileUploadDropZone 69 | onUpload={lookupEventHandler("upload")!} 70 | uid={extractValue(node.uid)} 71 | registerComponentApi={registerComponentApi} 72 | className={className} 73 | allowPaste={extractValue(node.props.allowPaste)} 74 | text={extractValue(node.props.text)} 75 | disabled={!extractValue.asOptionalBoolean(node.props.enabled, true)} 76 | updateState={updateState} 77 | acceptedFileTypes={extractValue.asOptionalString(node.props.acceptedFileTypes)} 78 | maxFiles={extractValue.asOptionalNumber(node.props.maxFiles)} 79 | > 80 | {renderChild(node.children, { type: "Stack" })} 81 | </FileUploadDropZone> 82 | ); 83 | }, 84 | ); 85 | ```